Android Support Material Button

In version 28.0.0 (currently in alpha) Android added a Material Button. Here is a quick snippet.

<com.google.android.material.button.MaterialButton
    android:id="@+id/material_button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/button_label_enabled">

This code makes it pretty easy to get a pretty button that is backwards compatabile. Check out the full list of attributes and some more examples in the references below.

Using ListAdapter with RecyclerView

Android added the ListAdpater in the 27.1.0 Support Library Update which allows for much more performant updating of list items. This library uses the DiffUtil to better calculate changes. Let’s start with some examples.

Getting Setup

First, I recommend starting a new project following the Android guide: https://developer.android.com/guide/topics/ui/layout/recyclerview. Get that set up and we can show the improvement.

Notice, for the previous set up you would have to do the following to change items in your adapter.

fun changeItem(items: String) {
  myDataset = items;
  notifyDataSetChanged()
}

Of you could use the following for inserting to save on performance.

fun addItem(item: String) {
  myDataset.add(item)
  notifyItemInserted(myDataset.size)
}

But what about editing and deleting? That is where the new ListAdpater comes in.

Using the List Adapter

So, first we will create a StringDiffCallback which will extend DiffUtil.ItemCallback. This allows us to creat a custom comparator for our items. In the real world, you would probably have a model that you need to check for difference rather than strings.

class StringDiffCallback : DiffUtil.ItemCallback() {
    override fun areItemsTheSame(oldItem: String?, newItem: String?): Boolean {
        return oldItem == newItem
    }

    override fun areContentsTheSame(oldItem: String?, newItem: String?): Boolean {
        return oldItem == newItem
    }
}

Then, we need to create a new adapter (or update the other one) to extend ListAdapter. We pass this new adapter an instance of our StringDiffCallback to use. Also, we’ve update to allow for the activity to pass a clickListner (for update the items).

class NewAdapter(private val clickListener: (String) -> Unit) :
        ListAdapter(StringDiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return ViewHolder(inflater.inflate(R.layout.my_text_view, parent, false))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position), clickListener)
    }

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        fun bind(item: String, clickListener: (String) -> Unit) {
            itemView.setOnClickListener { clickListener(item) }
        }
    }
}

Back in our activity we have the following to set up the NewAdapter.

override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)

      val myDataset: List = listOf("a", "b", "c")

      viewManager = LinearLayoutManager(this)

      newAdapter = NewAdapter(fun (item: String) {
          // Do some operations on dataset
          newAdapter.submitList(myDataset)
      })
      newAdapter.submitList(myDataset)

      recyclerView = findViewById(R.id.my_recycler_view).apply {
          setHasFixedSize(true)
          layoutManager = viewManager
          adapter = newAdapter
      }
}

The main item to note is the following where we can resubmit our list using the ListAdapter‘s submitList function. That function will use the diff util we passed and update our items accordingly and in a performant way.

newAdapter = NewAdapter(fun (item: String) {
   // Do some operations on dataset
   newAdapter.submitList(myDataset)
}

RxJava2 Completable Example

When using RxJava to listen to events, especially API responses from Retrofit, we typically use the Observable class. Similarly to using a Single, we don’t always need the onNext.

In fact, sometimes we don’t need any return info example for the complete event. For example, if we are posting a task to an API and we just want to know that the request was successful. For these casses we can use the Completable class.

Say we have this retrofit interface:

public interface APIClient {
    @POST("/task")
    Completable createTask(@Body String taskName);
}

Then we can simply use a Completable to let us know if the request was successful.

apiClient.createTask("a new task")
    .subscribe(() -> {
        // handle complete
    }, throwable -> {
        // handle error
    });

As with a Single, this helps us save a bit of boilerplate code throughout our code base.

RxJava2 Single Example

When using RxJava to listen to events, especially API responses from Retrofit, we typically use the Observable class. However, sometimes we don’t need the onNext call because we simply want the final result. For that, we can use a Single.

Say we have this retrofit interface:

public interface APIClient {
    @GET("/me")
    Single getMe();
}

Then we can simply use a Single to let us know when the user is fetched.

apiClient.getMe()
    .subscribe(user -> {
        // user returned
    }, throwable -> {
        // handle error
    });

It’s not a huge difference, but when you are repeating this code over a code base it can help to remove that extra function. It also makes the code more readable with the intent being to only handle the returned data.

Getting the Duration from ExoPlayer

In my case while using ExoPlayer, I needed to grab the duration of a audio file being stream from the web. ExoPlayer has a simple way to do this once the file is loaded. Here is the snipped.

mExoPlayer.addListener(new ExoPlayerEventListener() {
  @Override
  public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
    if (playbackState == ExoPlayer.STATE_READY) {
      long duration = audioPlayer.getDuration();
    }
  }

  @Override
  public void onPlayWhenReadyCommitted() {
  }

  @Override
  public void onPlayerError(ExoPlaybackException error) {
  }
 });

Recording Video on Android with CameraKit

In my projects, I have to do video recorinding often. It’s trendy of course. Lately, I have been using a library called CameraKit which adds some nice features on to the general implementation. This library is pretty good, although updates are slightly sparatic and the docs are out of date. In this tutorial, I’ll show you how to get started recording video with this library.

Side note, I usually like to use libraries and contribute to them rather than implementing myself to support open source :D.

Installing and Setting up

The first step is to install the library via gradle. Go to your Module:app gradle file and add the following to your dependencies:

compile 'com.wonderkiln:camerakit:0.13.1'

Sync cradle, then add a new activity. For this example we will use an Activity called RecordActivity. To create a new Activitiy to go File -> New -> Activity -> Blank Activity. This will create a new activity as well as a activity_record.xml file.

One last thing, we will need to add the storage permission to your AndroidManifest.xml (video and mic will be asked at runtime)

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Add the CameraView

Insided the activity_record.xml file, let’s add the following view

<com.wonderkiln.camerakit.CameraView xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/camera"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:ckCropOutput="false"
    app:ckDoubleTapToToggleFacing="true"
    app:ckFacing="back"
    app:ckFlash="off"
    app:ckFocus="tapWithMarker"
    app:ckJpegQuality="100"
    app:ckMethod="standard"
    app:ckPinchToZoom="true"
    app:ckVideoQuality="highest"
    app:ckZoom="2.0" />

This is the basic CameraView from the  CameraKit library. There are several options here, but most are just default. You can get a full list here: http://docs.camerakit.website/#/?id=extra-attributes

Let’s also add a button that we can use to start an stop the camera.

<Button
    android:layout_width="wrap_content"
    android:layout_height="match_parent"
    android:id="recordButton"
    android:text="Record"
    />

Setting up the Code

Now that we have our UI, let’s wire that up. Open up the RecordActivity.java and add the following private variables.

private CameraView cameraView;
private boolean isRecording = false;

These will hold our CameraView and the current recording state. Let’s now create a function (to keep logic isolated) that will initialized our camera. Call it setUpCameraView​. Inside the function, let’s grab the CameraView and add the CameraKitEventListener which will allow us to listen to the CameraView events.

cameraView = (CameraView) findViewById(R.id.camera);
cameraView.addCameraKitListener(new CameraKitEventListener() {
  @Override
  public void onEvent(CameraKitEvent cameraKitEvent) {
  }

  @Override
  public void onError(CameraKitError cameraKitError) {
  }

  @Override
  public void onImage(CameraKitImage cameraKitImage) {
  }

  @Override
  public void onVideo(CameraKitVideo cameraKitVideo) {
    // The File parameter is an MP4 file.
    Log.d("testing-tag", String.valueOf(cameraKitVideo.getVideoFile()));
  }
});

There are a few events here that can be helpful to hook into and notify your users, but the main function we are concerned with is onVideo. I also added a log function to print the path to the video file. You can use this file in your app of course 😀

Finally, let’s wire up the button to record.

Button buttonView = (Button) findViewById(R.id.captureButton);
buttonView.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    if (isRecording) {
      cameraView.stopVideo();
      isRecording = false;
      return;
    }

    cameraView.captureVideo();
    isRecording = true;
  }
});

Here we simply start the video capture when the user clicks and set the status to isRecording. We of course reverse that when the user clicks again.

Adding Lifecycle

One last thing we have to do when working with the camera is to be sure to unlock and destroy our media listener. Most of that is hidden by CameraKit so we just need to add the following to your activity life cycle events.

@Override
public void onResume() {
  super.onResume();
  cameraView.start();
}

@Override
protected void onPause() {
  cameraView.stop();
  super.onPause();
}

The Final Result

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import com.wonderkiln.camerakit.CameraKitError;
import com.wonderkiln.camerakit.CameraKitEvent;
import com.wonderkiln.camerakit.CameraKitEventListener;
import com.wonderkiln.camerakit.CameraKitImage;
import com.wonderkiln.camerakit.CameraKitVideo;
import com.wonderkiln.camerakit.CameraView;

public class RecordActivity extends AppCompatActivity {

  private CameraView cameraView;
  private boolean isRecording = false;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_record);

    setUpCameraView();
  }

  @Override
  public void onResume() {
    super.onResume();
    cameraView.start();
  }

  @Override
  protected void onPause() {
    cameraView.stop();
    super.onPause();
  }

  private void setUpCameraView() {
    cameraView = (CameraView) findViewById(R.id.camera);
    cameraView.addCameraKitListener(new CameraKitEventListener() {
      @Override
      public void onEvent(CameraKitEvent cameraKitEvent) {
      }

      @Override
      public void onError(CameraKitError cameraKitError) {
      }

      @Override
      public void onImage(CameraKitImage cameraKitImage) {
      }

      @Override
      public void onVideo(CameraKitVideo cameraKitVideo) {
        // The File parameter is an MP4 file.
        Log.d("testing-tag", String.valueOf(cameraKitVideo.getVideoFile()));
      }
    });

    Button buttonView = (Button) findViewById(R.id.captureButton);
    buttonView.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View view) {
        if (isRecording) {
          cameraView.stopVideo();
          isRecording = false;
          return;
        }

        cameraView.captureVideo();
        isRecording = true;
      }
    });
  }
}

And that’s it! I hope this gets you started recording video on Android. Feel free to leave any questions or comment below.