360andev chris guzman share fb

Android Libraries I Wish I Knew About When I Started

There’s no sense in re-inventing the wheel when you’re working on Android apps, especially if you’re just starting out. In this 360AnDev talk, we will cover a range of libraries you can include in your Android apps that will help you tackle the problems other people have already solved. Whether you’re loading data from a web API, showing & caching images, or storing & syncing data, there’s a library you can pull in to help you out!


Introduction (0:00)

My name is Chris Guzman, and I’m an engineer at Groupon. A little info about me, I was a Ruby on Rails developer until about a year ago when I made the switch to Android. In this talk, I’m going to show libraries I wish I would have known when I got started in Android development.

Starting a new app? (0:44)

Let’s say you’re starting a new app, or you have a feature, and you’re not sure which library you might want to use. Best practice is “what’s the community using,” and we’re going to cover that in this talk. A lot of basic apps do similar things:

  • Manage multiple views.
  • Load and display images.
  • Get data from an API.
  • Parse JSON, and updating our views with that JSON.
  • Persist data to storage.
  • Start new activities with any kind of extras.

These are the libraries we’re going to talk about today, and their versions:

One thing I’m not going to do in this talk today is tell you how to set it up with your build.gradle file. I think the READMEs all have really good instructions on that, no matter what your experience level is. We’re going to get into what the features of the library are, and why and how you should use them.

TAaSTY - Tacos As a Service To You (2:04)

Welcome to the 45 minute Hackathon! I hope you all came prepared. We are going to make something today. So what should I make? Well someone told me you should always make something you’re super passionate about. I think I have the right idea. I’d like to introduce you to my new app, TAaSTY. That stands for Tacos As a Service To You. I think you’re going to love this app. It’s essentially Tinder for tacos. We’re going to have a text view, an image view, two buttons, and an info button down at the bottom as well. Let’s get started on TAaSTY. The first thing we need to do is set up our views, to inform the audience what we’re doing here:


<LinearLayout ... android:orientation="vertical">
    <ImageView android:id="@+id/taco_img" .../>
    <TextView android:id="@+id/description" .../>
    <LinearLayout android:orientation="horizontal" .../>
        <Button android:id="@+id/reject" .../>
        <Button android:id="@+id/info" .../>
        <Button android:id="@+id/save" .../>
    </LinearLayout>
    <EditText android:id="@+id/tag" .../>
</LinearLayout>

Here’s what it’s going to look like. We’re going to have an ImageView, a TextView underneath that, then three buttons underneath that. Then if we want at the end, we might add an EditText down below, so we can add some tags onto that image.

Let’s get started. We need to use the views, and we’re going to use our first library, which is Butter Knife.

Butter Knife (3:01)

Butter Knife is a great library that lets us use annotations to allow us to write less boilerplate code. What are some benefits of Butter Knife? I’m betting you’ve written findViewById a million times. You know when you’re starting up a new activity you already have views, you’ve done that a lot. Butter Knife is a great library that helps reduce the amount of times we have to do that. Butter Knife is great because there’s no cost at runtime. All the work Butter Knife does is done at compilation time, so you don’t have to worry about slowing down your app for your users. Butter Knife also gives us improved view lookups, listener attachments, and any kind of resource lookups. Let’s get into it:


<TextView android:id="@+id/description"
    ...
    />

public class MainActivity extends Activity {
    @BindView(R.id.description) TextView description;

    @Override
    protected void onCreate(Bundle bundle) {
        ...
        ButterKnife.bind(this);
        description.setText("Tofu with Cheese on a tortilla");
    }
}

That is what it’ll look like using Butter Knife in an activity. We have this TextView with the ID of description. In the main activity, all we need to do is set a member variable and name it, so we’re going to name it description. We use the BindView annotation and pass in the ID of that view. What Butter Knife then lets us do, is use that member variable anywhere throughout that activity.

First, we need to make sure we call that ButterKnife.bind method, and then after that, we can use that member variable wherever we need to. What is ButterKnife.bind doing?


public void bind(MainActivity activity) {
    activity.description = (android.widget.TextView) activity.findViewById(2130968577);
}

That is one example of what it’s doing. Butter Knife is generating code that looks up views or resources, and it saves it as a property. In this example, it’s saving as a property on the activity. At compilation time, this is what Butter Knife would be doing in this bind method. Setting description as a property on that activity, and it’s calling the findViewById for us. We don’t have to waste any more time doing that.

Let’s say you’re using a fragment. If you are I feel bad for you:


public class TacoFragment extends Fragment {
    @BindView(R.id.tag) EditText tag;
    private Unbinder unbinder;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup group, Bundle bundle) {
        ...
        //Important!
        unbinder = ButterKnife.bind(this, parentView);
        tag.setHint("Add tag. Eg: Tasty!, Want to try")
        return view;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        //sets the views to null
        unbinder.unbind();
    }
}

If you’re using a fragment, you have to set up a little bit differently, but it’s still simple. We need to pass in the two arguments. You can also see that we have set reference to an unbinder. The reason we are doing this is because fragments have different life cycles than activities. If for some reason the fragment sticks around, we might have garbage collection or memory errors, because the fragment might still be holding on to the reference, to those views, that we’ve set up with Butter Knife. By calling unbinder.unbind() in the destroy view, or whenever we’re winding down the fragment, it sets those references to those views as null. It frees it up and lets it be released when garbage collection happens.

Get more development news like this

One of my favorite things about Butter Knife is the event listeners:


@OnClick(R.id.save)
public void saveTaco(Button button) {
    button.setText("Saved!");
}

@OnClick(R.id.reject)
public void reject() {
    Log.d("RejectBtn", "onClick")
}

Let’s say you need to attach an OnClick listener. All you do is write a method, annotate it with OnClick, and pass in the ID of the view that you want that listener attached to. For this example, I’m attaching an OnClick listener to my save button. Another thing that I’m doing, which is really handy, is passing in the reference to that button into that view in that method. We can dynamically update that button when that event listener happens. For this example, I’m setting the text. We could do a number of things. We could set the alpha. We could hide it, or whatever we need to do.

Butter Knife’s pretty flexible too because it lets us not have to pass in the reference to the view. When we click on the reject button, maybe I don’t care about updating the view for that button anymore. I just want to log out that the user clicked it. Or maybe I have 50,000 analytics libraries, and I needed to tell all of them that the user clicked reject. Right out of the box, Butter Knife supports these listeners.

Inject resources (7:07)

Butter Knife also lets us inject resources:


class MainActivity extends Activity {
    @BindString(R.string.title) String title;
    @BindDrawable(R.drawable.star) Drawable star;
    // int or ColorStateList
    @BindColor(R.color.guac_green) int guacGreen;
    // int (in pixels) or float (for exact value)
    @BindDimen(R.dimen.spacer) Float spacer;
}

Let’s say you have a strings file, and you want to bring in a string from it. All you need to do is look up that string in the strings file, pass in the ID, and you can set it as a property on the activity. We can do the same thing with drawables, colors, or dimensions as well. We can send in ints for the colors or the dimension, or we can send in other properties. Another cool thing about Butter Knife is grouping views together:


@OnClick({ R.id.save, R.id.reject})
public void actOnTaco(View view) {
    if (view.getId() == R.reject) {
        Toast.makeText(this, "Ew Gross!", LENGTH_SHORT).show();
    }
    else {
        Toast.makeText(this, "Yummy :)", LENGTH_SHORT).show();
    }
    //TODO: implement
    getNextTaco();
}

In this instance, I want the same thing to happen when I click on any of those buttons, the save or the reject button. What I’m going to do is annotate it with that OnClick annotation, and I pass in that list of views. Then, on that method, I’ve passed in the reference to that view, and I can do whatever I want to do based on that views ID, or any other property that we can access on that view.

In this instance, if you clicked reject, I’m going to make a Toast that says, “Ew Gross!”. If you hit accept, we’re going to say, “Yummy.” But no matter what I want this getNextTaco method to be called, so that’s what I might want to bind these views together in this OnClick listener. I’m going to talk about what this getNextTaco method might do in the future slides.

While we’re talking about binding views into a list, here’s another thing that we can do. We can apply a property to all the views at once:


@BindViews({R.id.save, R.id.reject})
List<Button> actionButtons;

ButterKnife.apply(actionButtons, View.ALPHA, 0.0f);

What I’ve done is made this list of buttons, save and reject, and I can change the alpha of these buttons programmatically by using the ButterKnife.apply method. Butter Knife also lets us fine-tune the behavior:


ButterKnife.apply(actionButtons, DISABLE);
ButterKnife.apply(actionButtons, ENABLED, false);

static final ButterKnife.Action<View> DISABLE = new ButterKnife.Action<View>() {
    @Override public void apply(View view, int index) {
        view.setEnabled(false);
    }
};
static final ButterKnife.Setter<View, Boolean> ENABLED = new ButterKnife.Setter<View, Boolean>() {
    @Override public void set(View view, Boolean value, int index) {
        view.setEnabled(value);
    }
};

If we call ButterKnife.apply, and pass in that list of buttons, we can also pass in what we want to happen to them when we use the apply method. In the first example, I’m disabling them. I’m setting all of them enabled to false. In the second example, I’m doing a little bit more fine tuning. I can send in a third argument, and change the properties of the views based on it. I’m saying setEnabled based on that third argument. You can also change the color based on the ID that you might pass in.

We made our getNextTaco method:


private void getNextTaco() {
    ButterKnife.apply(actionButtons, DISABLE);
    //TODO: implement
    setTacoImage();
}

It’s going to disable the buttons so that our users don’t click on it twice. Then we’re going to set the Taco image. We don’t know how to do that yet, so we’re going to move on to how we’re going to add pictures of TAaSTY Tacos. We’re going to use Picasso for this.

Picasso (9:47)

Picasso’s a great library that lets us download images and display them in image views. Some of the benefits of Picasso are that it automatically makes the HTTP request for you. You don’t have to worry about that kind of stuff. Picasso will also cache the image, so you don’t have to worry about making that request again. It’s easy to crop, center, scale, or perform any kind of work that you need to do on that image. It also takes care of downloading off of the main thread, so we don’t have to worry about that kind of networking on main thread exceptions.

If you are using a RecyclerView, Picasso works really well with RecyclerViews as attaching to any kind of image you use, or unattaching whenever that view gets recycled off screen.

Pop quiz: which would you prefer? When I first started looking at how to download an image, when I was switching over to Android, I looked at Stack Overflow about how I might download an image, and I got this:


private Bitmap DownloadImage(String url)
{
    Bitmap bitmap = null;
    InputStream in = null;

    try
    {
        in = OpenHttpGETConnection(url);
        bitmap = BitmapFactory.decodeStream(in); in.close();
    }
    catch (Exception e)
    {
        Log.d("DownloadImage", e.getLocalizedMessage());
    }

    return bitmap;
}

I’m using an input stream, and I’m downloading something, and if it blows up, I have a try catch in here. I made the cardinal sin of copy and pasting code off of Stack Overflow. But if I was to do this, I don’t really understand what’s going on here. That is not what I want to do.

This is what we get to do with Picasso:


Picasso.with(context)
        .load("http://placekitten.com/200/300")
        .into(imageView);

We pass in the context, the image URL, and the imageView that we want to place it into. That’s a real URL by the way. All you need to do is pass in the dimensions, and it gives you a kitten of that size. Picasso’s really friendly, which is really nice. It also gives us some really cool things:


.placeholder(R.mipmap.loading) //can be a resource or a drawable
.error(R.drawable.sad_taco) //fallback image if error
.fit() //reduce the image size to the dimensions of imageView
.resize(imgWidth, imgHeight) //resizes the image in pixels
.centerCrop() //or .centerInside()
.rotate(90f) //or rotate(degrees, pivotX, pivotY)
.noFade() //don't fade all fancy-like

  • We can set a placeholder image. We have a resource or drawable local in the app, and we can set that as a placeholder image. Since Picasso has to go and download it, while we wait, we can show it a placeholder image. If there’s any error, where the image doesn’t get downloaded for some reason, we can default to showing an image.
  • We can fit the dimensions of the image automatically to the dimensions of the image view, which is super nice. Or we can resize it ourselves, by adding in the pixels that we want to resize it down to.
  • We can center crop it, center inside it, rotate it on its center, or if we want to rotate it at a point off its center we can do that.
  • And Picasso automatically fades images in, kind of like Medium.com does. If we don’t want to do that, we can call noFade.

Picasso’s not just for loading images on the web. If you want to load a drawable, a file string, or new file, you can do that all as well:


Picasso.with(context).load(R.drawable.salsa).into(imageView1);
Picasso.with(context).load("file:///asset/salsa.png").into(imageView2);
Picasso.with(context).load(new File(...)).into(imageView3);

Now we know how to load images. This is how we might do it in our app:


//Butter Knife!
@BindView(R.id.taco_img) ImageView tacoImg;

private void setTacoImage() {
    Picasso.with(context)
        .load("http://tacoimages.com/random.jpg")
        .into(tacoImg);
}

private void getNextTaco() {
    ButterKnife.apply(actionButtons, DISABLE);
    setTacoImage();
    //TODO: implement
    loadTacoDescription();
}

In our app, we might use Butter Knife to grab the ImageView and load that random taco. That is how we’re going to set the Taco image. What we need to do is load the next taco description. That is where we’re going to make the API call to get this taco. But first we need to take a real quick detour, and we need to set up our models.

We’re going to set up our models by using what the JSON will look like to inform us how we’re going to set them up. Speaking of JSON, I’m going to introduce you to our next library which is Gson.

Gson (13:29)

Gson is a library that allows us to convert JSON to a Java object, and vice versa. Some benefits of Gson are it doesn’t require annotations in our classes; it’s super performant and commonly used. I saw some stats where Gson handles a whole crapton; I think that’s the perfect term, of information at once if you need it to. This is our model:


class Taco {
    private String description;
    private String imageUrl;
    private String tag;
    //not included in JSON serialization or deserialization
    private transient boolean favorite;
    Taco(String description, String imageUrl, String tag, boolean favorite) {
    ....
    }
}

That is a plain old Java object model. It’s going to have a description string, an imageUrl string, and a tag. Then we’re also going to have this transient property of favorite. With Gson if you label a property as transient when you serialize it into JSON, that field gets skipped. Then we have our constructor, and that’s how we can create a new taco. This is what it will look like to convert a Java object into a Gson object:


// Serialize to JSON
Taco breakfastTaco = new Taco("Eggs with syrup on pancake", "imgur.com/123", "breakfast", true);
Gson gson = new Gson();
String json = gson.toJson(breakfastTaco);

// ==> json is {description:"Eggs with syrup on pancake", imageUrl:"imgur.com/123", tag:"breakfast"}

// Deserialize to POJO
Taco yummyTaco = gson.fromJson(json, Taco.class);
// ==> yummyTaco is just like breakfastTaco except for the favorite boolean

We’re going to take our constructor and place in the description. That is how I had my breakfast today; a breakfastTaco. We can pass in the description, our image URL, a tag, and we can also send in if it’s favorited or not. All we need to do is grab a new instance of Gson, and then call the two JSON methods on it, and pass in that Java object. You can see on that third comment; that’s what the JSON will look like after we serialize it into JSON. You’ll notice that it’s missing the favorite true because we’ve set that to a transient property.

Let’s say we need to do the reverse, and we need to deserialize it from JSON into a Java object. We would call the fromJSON method on that instance of Gson, pass in the JSON string, and also the class that it should deserialize into.

Now you can notice that yummyTaco will equal an object of breakfastTaco. It’s going to have the same properties, except the favorite boolean, as I said because of the transient property. Benefits about Gson? Any fields in your current class or your super classes are included by default. It supports multidimensional arrays, so if for some reason you’re playing chess via HTTP, Gson has your back.

Some gotchas with Gson, if you’re serializing from a Java object into JSON, if you have a null field, it’s going to get skipped. Let’s take the example of a taco that maybe is missing a tag. The tag is null. When we convert that into JSON, that key value pair in that JSON blob is not even going to show up. If you’re deserializing from JSON, and that JSON has a null key value pair that doesn’t match up with a property, that’s going to be null as well. Let’s say we download a taco JSON, and it doesn’t have a description, the description’s going to be null in the Java object. Maybe a gotcha, maybe a kind of expected behavior, I’ll let you choose.

We can customize Gson. This is just the tip of the iceberg for customizing Gson:


//Set properties to null instead of ignoring them
Gson gson = new GsonBuilder().serializeNulls().create();

//Keep whitespace
Gson gson = new GsonBuilder().setPrettyPrinting().create();

The first gotcha I said about it was serializing nulls; it would skip over nulls in serializing the JSON. We can change that by using the serialized nulls in the GsonBuilder. Or if we need to keep whitespace, because Gson automatically strips out whitespace by default, we can use the setPrettyPrinting.


public class Taco {

    @SerializedName("serialized_labels")
    private String tag;

}

Sometimes we work with horrible APIs. In this case, maybe we have an API that gives us back a property that is snake case and is for some reason misspelled, or we don’t want to use the name that it gives us. All we need to do is when we write our property, tag it with the annotation and pass in the name that the API’s giving us. Then we can still use the Java object by calling this tag property, instead of having to use a serialized labels property that might come back from JSON.

Maybe sometimes you have to work with a custom date format. This is how you would do that in Gson:


public String DATE_FORMAT = "yyyy-MM-dd";

GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.setDateFormat(DATE_FORMAT);
Gson gson = gsonBuilder.create();

Give it a date format and when you create Gson, pass on that date format.

Now we can get into loading the taco description:


private void getNextTaco() {
    ButterKnife.apply(actionButtons, DISABLE);
    setTacoImage();
    //TODO: implement
    loadTacoDescription();
}

Making a call to the web for that taco description. To do that we’re going to use Retrofit, not AsyncTask.

Retrofit (18:27)

If you’re using AsyncTask for all of your networking, I would consider using Retrofit. Reasons why you might want to use Retrofit:

  • Type-safe.
  • Built-in support for authentication.
  • Parsing JSON into POJOs when you pass in a Gson factory.

We just talked about Gson, so we’re going to pass in that Gson factory. Retrofit also supports RxJava which is, if you didn’t know, the new hotness. Retrofit also lets us execute HTTP requests synchronously or asynchronously out of the gate. What does Retrofit look like?


public interface TacoApi {
    // Request method and URL specified in the annotation
    // Callback for the parsed response is the last parameter

    @GET("random/")
    Call<Taco> randomTaco(@Query("full-taco") boolean full);

    @GET("contributions/")
    Call<List<Contributor>> getContributors();

    @GET("contributions/{name}")
    Call<Contributor> getContributors(@Path("name") String username));

    @POST("recipe/new")
    Call<Recipe> createRecipe(@Body Recipe recipe);
}

We need to create an interface. As I’m making my interface TacoApi you can see that we’re going to have four API in points. We can get a randomTaco. We can get a list of contributors. We can get a contributor by name, or we can post a new recipe. Let’s talk about it one by one.

When we want to get a random Taco all we need to do is call the randomTaco method, and you can see that it takes an argument of a boolean, full, true, or false. We’ve annotated to it with this query, full-taco.

What happens when you annotate an argument using Retrofit is that, if you annotate using the query annotation, it’s going to pass that along as a query parameter to the server. In our instance, if we call randomTaco true, it’s going to say full-taco equals true. Now you may ask me, “Chris, why would you ever want full-taco equals false? We all like tacos, and I like my tacos full.” It just so happens that with this API if you pass in full-taco true you get the whole description. If you get full-taco false, it gives you all the ingredients. You have to make it by hand.

In our second API in point, you can see that it takes no arguments and what it returns to us is a list of contributors. You can also get stuff that’s a list of things, so you can iterate over that list.

In this third API, we have another argument in our getContributors method and in this case what we’re doing is we’re passing in a URL to the path. What getContributors does is pass in a contributor’s name, and it tells you all the recipes that contributor has made. You need to pass in the name into the path. You’d annotate it with path, give it the string username, and that URL with the curly braces. That part would get replaced with the argument that you passed in.

Last but not least, if you need to post a JSON payload, when you annotate a method with @POST, you can also in the argument have a body annotation on an argument, and that’s going to serialize that Java object into a JSON object that will be sent up to the server. Retrofit works with all the usual HTTP verbs.

This is what it looks like to get JSON synchronously:


Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("http://taco-randomizer.herokuapp.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

// Create an instance of our TacoApi interface.
TacoApi tacoApi = retrofit.create(TacoApi.class);

// Create a call instance for a random taco
Call<Taco> call = tacoApi.randomTaco(true);

// Fetch a random taco
// Do this off the main thread
Taco taco = call.execute().body();

Again this is a real API, http://taco-randomizer.herokuapp.com/. What we need to do is create a new instance of Retrofit, pass in the base URL, add the JSON converter factories, so that it converts it from JSON to POJO and vice versa, and build. Get an instance of the taco API, and then we can grab an instance of the Call object.

This Call object is something that comes from OkHTTP and Retrofit. You can make the call synchronously, asynchronously, cancel the call, either while it’s working or before it even starts, or you can also clone a call object so you can make that same request over again. If we’re going to aim to get the JSON synchronously, we’re going to execute the call, and a bit of happy path here, the body should return us a taco object. I suggest you do this off the main thread, or you can work it asynchronously.

Let’s say we’re posting a new recipe:


Recipe recipe = new Recipe();
Call<Recipe> call = tacoApi.createRecipe(recipe);
call.enqueue(new Callback<Recipe>() {
    @Override
    public void onResponse(Call<Recipe> call, Response<Recipe> response) {

    }

    @Override
    public void onFailure(Call<Recipe> call, Throwable t) {

    }

All we need to do is create a new Recipe, pass that into the createRecipe() method, and then we use the call.enqueue() method. What enqueue() will do is make the request in an asynchronous manner, and gives us two callbacks: a response and a failure. We can do whatever we need to do with either of those conditions.

Really great tricks, at least the first trick applies to Retrofit too, is that we can change the base URL.

If we’re working for some reason with a new version, our API’s version is out and we need to make a request to a new version of API, we can pass in the full URL and we can annotate that method:


//Change the base url
@POST("http://taco-randomizer.herokuapp.com/v2/taco")
private Call<Taco> getFromNewAPI();

//Add headers
@Headers({"User-Agent: tacobot"})
@GET("contributions/")
private Call<List<Contributor>> getContributors();

That way we can skip the base URL. We can also add headers if we need to. This is what it looks like in our app when we’re going to get a random taco:


private void getNextTaco() {
    ...
    loadTacoDescription();
}

private void loadTacoDescription() {
    Call<Taco> call = tacoApi.randomTaco(false);
    call.enqueue(new Callback<Taco>() {
        @Override
        public void onResponse(Call<Taco> call, Response<Taco> response) {
        //Set description from response
        Taco taco = response.body;
        //TODO: implement
        saveTaco(taco);
    }
    
    @Override
    public void onFailure(Call<Taco> call, Throwable t) {
        //Show error
    }
}

We’re going to request a random taco, and I’m going to make the request asynchronously. When it succeeds, I’m going to save the taco. When it fails, I don’t care because it’s a 45 minute Hackathon. I’m going to keep going.

Realm (24:20)

Let’s talk about the saveTaco method and how that might work. When we want to save a taco recipe for later, we’re going to use Realm. Realm is a great library that replaces SQLite for us.

Here are some benefits of Realm:

  • It’s really easy and simple to set up.
  • It works by extending our models. With Realm the models are our schema for our database.
  • It was made for mobile from the ground up.
  • A lot of queries in Realm are fast enough to be run synchronously. I would suggest you do some of them in an asynchronous manner, but if you like to live on the edge, you can run them synchronously if you like.
  • Realm also gives us the support to have multiple Realm databases on one app. That is a little bit outside the scheme of this talk, but there is info online about that.

There is a two-step process for setting up Realm. First, we need to take our objects, and we need to extend from RealmObject:


public class Taco extends RealmObject {
    private String description;
    private String tag;
    private String imageUrl;
    private boolean favorite;
    //getters and setters
}

That is the same taco object we’ve been working with this entire time. Then to set up Realm, we need to get an instance of the RealmConfiguration and get a default instance of Realm out of that:


Set-up Realm
// Create a RealmConfiguration
// saves the Realm file in the app's "files" directory.
RealmConfiguration realmConfig =
    new RealmConfiguration.Builder(context).build();
Realm.setDefaultConfiguration(realmConfig);

// Get a Realm instance for this thread
Realm realm = Realm.getDefaultInstance();

Every time you work with Realm on a new thread, you need to get a new instance of Realm. This is one simple example of how we might persist some object into the database:


// Persist your data in a transaction
realm.beginTransaction();

// Persist unmanaged objects
final Taco managedTaco = realm.copyToRealm(unmanagedTaco);

// Create managed objects directly
Taco taco = realm.createObject(Taco.class);

realm.commitTransaction();

We make sure all our data happens in transactions. The first thing we’re doing is beginning a transaction. Realm will automatically manage all of your objects in memory. You need to copy it into Realm, or you need to create the object from Realm.

The first example is showing where, let’s say we already have a taco object, we’ve set the description in the URL, but we want to persist it into the database. We’d call realm.copyToRealm, and pass in the object that we’ve already created.

Alternatively, if you have a new taco object, and you want to create an instance from Realm, you can use that with realm.createObject and pass in the class of the object. We need to call realm.commitTransaction, and voila, our info is persisted into the database.

What about accessing data? What if we want to get all of our liked tacos, to see how many tacos never liked us back? What we can do with Realm is we can ask for a RealmResults:


// Get a Realm instance for this thread
Realm realm = Realm.getDefaultInstance();

//find all favorite tacos
final RealmResults<Taco> likedTacos =
    realm.where(Taco.class).equalTo("favorite", true).findAll();

We’re asking Realm, “Hey Realm, can you give me all the tacos that I favorited and return them to me?” It’s going to return them to us in a RealmResults. We can iterate over them, and we can treat them as taco objects.

Another way of writing data, again we need to get our instance of Realm if we’re in a new thread for some reason. We can use this transaction block:


// Get a Realm instance for this thread
Realm realm = Realm.getDefaultInstance();

//Transaction block
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Taco taco = realm.createObject(Taco.class);
        taco.setDescription("Spaghetti Squash on Fresh Corn Tortillas");
        user.setImageUrl("http://tacoimages.com/1.jpg");
    }
});

I’m executing this transaction. I’m creating a new object from Realm, setting the description, setting the image URL, and then when that block executes, it’ll be persisted into the database. What about executing it asynchronously?


//Async
realm.executeTransactionAsync(new Realm.Transaction() {
        @Override
        public void execute(Realm bgRealm) {
            Taco taco = bgRealm.createObject(Taco.class);
            taco.setDescription("Spaghetti Squash on Fresh Corn Tortillas");
            user.setImageUrl("http://tacoimages.com/1.jpg");
        }
    }, new Realm.Transaction.OnSuccess() {
        @Override
        public void onSuccess() {
            // Transaction was a success.
        }
    }, new Realm.Transaction.OnError() {
        @Override
        public void onError(Throwable error) {
            // Transaction failed and was automatically canceled.
        }
    });

We would call the executeTransactionAsync, again using our execute block, setting any kind of setters that we need to. Then Realm gives us our two callbacks onSuccess and onError. If you need to do anything with that, that’s when you can alert your users with an error or something like that.

A little-known fact about tacos, they have many ingredients. How might we have a relation between tacos? Or not between tacos, but tacos and their ingredients?


public class Taco extends RealmObject {
    ...
    private List<Ingredient>
    ...
}

public class Ingredient extends RealmObject {
    private String name;
    private URL url;
}

Let’s say for instance that where a taco might have a list of ingredients, and this ingredient object is going to have a name in your URL. All we need to do is make sure both of these objects extend from Realm and Realm will handle how they are associated. Let’s look at an example of that:


RealmResults<Taco> limeTacos = realm.where(Taco.class)
                                    .equalTo("ingredients.name", "Lime")
                                    .findAll();

Here we’re asking Realm, “Realm can you give me all the tacos where an ingredients name is lime?” We’re going to save that to our Realm results of limeTacos. That’s going to give us our list of tacos that we can iterate through. Realm gives us all our typical SQL relationships. One to one, one to many, etc.

Realm will also give us conditions or predicates for looking up any kind of RealmResults.

Occasionally there comes a time in our lives where we need to delete some tacos:


// All changes to data must happen in a transaction
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        // remove single match
        limeTacos.deleteFirstFromRealm();
        //or limeTacos.deleteLastFromRealm();

        // remove a single object
        Taco fishTaco = limeTacos.get(1);
        fishTaco.deleteFromRealm();

        // Delete all matches
        limeTacos.deleteAllFromRealm();
    }
});

In this instance, we have a list of limeTacos and maybe we used the wrong kind of lime; limestone instead of real limes. We need to start deleting some limes from our favorites list.

What we can do in our Realm transaction block is take this list and we can delete the first or delete the last. We can also get an object via its index and delete it. Or we can even delete everything if we need to.

A great thing with Realm is that we can attach change listeners onto a Realm object or our RealmResults:


limeTacos.addChangeListener(
    new RealmChangeListener<RealmResults<Taco>>() {
        @Override
        public void onChange(RealmResults<Taco> tacosConLimon) {
        //tacosConLimon.size() == limeTacos.size()

        // Query results are updated in real time
        Log.d("LimeTacos", "Now we have" + limeTacos.size() + " tacos");
        }
    }
);

In this example, I’ve attached a change listener to my list of limeTacos. Whenever something gets added, or something in the list gets changed, this change listener gets called, and I have a callback. And this callback gives me an argument, and I’ve named it tacosConLimon.

The great thing about Realm is it auto-updates objects. We would see that once we added something to limeTacos, we would see that the size of these two lists are the same because these two lists refer to the same lime results. We can do whatever we need to do with that list of tacos.

Realm tips (30:24)

There are a few benefits and tips for using Realm. If you in your model have an ID with a primary key annotation, so an ID of integer (I think it also allows strings as a primary key), we can allow the use of copy to Realm or update. Meaning we can update the same object in memory instead of creating new ones all the time.

Realm works really nice with Gson and Retrofit. With the newer version of Realm, we can have custom getters and setters. In other versions before that, we didn’t have that ability, so this is a really nice bonus for us.

One thing you need to make sure of when you use Realm to prevent any kind of garbage collection or any kind of weird memories, any kind of weird errors, is remove any change listeners that you have attached to a Realm object or to a RealmResult. You can remove the change listener if you have a reference to it, or you can remove all change listeners:


@Override
protected void onDestroy() {

    // Remove the listener.
    realm.removeChangeListener(realmListener);
    //or realm.removeAllChangeListeners();

    // Close the Realm instance.
    realm.close();
    ...
}

Also remember to close the instance of Realm any time you’re winding down a life cycle, activity, fragment, or some kind of thread you might be on. That helps prevent any errors or memory leaks.

Realm has one really big gotcha. I know that they’re working on it, and I look forward to the solution that they’ll have in the future, but currently you can’t persist a list of strings or primitives on an object. One example of this is, my taco object had a tag string, and it was just one tag. If you noticed, it didn’t have a list of tags, and this is because currently, Realm doesn’t have support for persisting that list of strings to the database. If you are downloading data from an API, you can add this Gson adapter that I’ve linked to in my presentation. I think another workaround is creating a new object of a string, like a RealmString.

You also need to remember that if you’re saving data to Realm, and you’re getting it from an API, you copy that object from memory into Realm, and you use that copy from Realm instance. If you are working with large data sets or complex queries, you should make sure that you run those on the main thread.

Last but not least, we’re going to start a new activity:


//TODO: implement
goToTacoDetailActivity();

We’ve been at this Hackathon for 30-something minutes, and we’re still on one activity. Let’s make our next activity.

Dart & Henson / Conclusion (33:04)

We’re going to use Dart & Henson. Dart is the name of the library. Dart is really nice because it’s inspired by Butter Knife, and we use it a lot at Groupon.

Dart is a way to inject intent extras as a property onto an object. When you’re starting a new activity, and you want to pass along these extras on the intent, you can easily retrieve it with Dart:


//MainActivity
intent.putExtra(EXTRA_TACO_DESCRIPTION, "Seasoned Lentils with Green Chile on Naan");
//TacoDetailActivity
tacoDescription = getIntent().getExtras().getString(EXTRA_TACO_DESCRIPTION);

Just like findViewById, I am sure that we’ve spent a lot of time writing stuff like intent.putExtra, key, value, getIntent, getExtras, getString, pass in the key. We always have to go back and find what the key is, set it as a variable somewhere or a constant. Dart & Henson removed all that headache. Henson gives us a readable, domain specific language, for passing extras on to the intent.

Let’s talk about it in two parts. First, we’re going to talk about Dart. This is what it looks like, grabbing extras from an intent:


public class TacoDetailActivity extends Activity {
    //Required. Exception thrown if missing
    @InjectExtra boolean favorite;
    @InjectExtra String description
    //default value if left null
    @Nullable @InjectExtra String tag = "taco";
    //Ingredient implements Parcelable
    @Nullable @InjectExtra Ingredient withIngredient;

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        Dart.inject(this);
        //TODO use member variables
        ...
    }
}

It looks a little bit like Butter Knife. We have these four extras: favorite, description, tag, and ingredient. Let’s talk about the first two.

We’ve annotated these extras with these properties on this activity with the annotate InjectExtra. If you just leave this annotation the way it is, InjectExtra, if these extras on the intent are null when the activity starts, you’ll get an exception thrown. We expect that this activity starts with extras such as the boolean favorite and the description string. However, if you want to make those extras an optional field, we could also use this Nullable annotation.

Our app will not blow up and will be fine if the other activity doesn’t send in any kind of extras on the intent. We can also set a default value. For tag, if we do not send in a tag as an extra on the intent, we can default it to taco just in case you forgot that we were talking about tacos.

We can also inject an entire model as long as that model implements Parcelable. Dart will handle unwrapping that and sending that as a property on this activity. Once we use Dart inject, we can start using those member variables that we’ve defined. How would we go about setting these extras on the intent? This is what we can use Henson for:


Generate intent builders with Henson
//Start intent for TacoDetailActivity
Intent intent = Henson.with(context)
    .gotoTacoDetailActivity()
    .favorite(true)
    .description("Seasoned Lentils with Green Chile on Naan")
    .ingredient(new Ingredient())
    .build();
// tag is null or defaults to "taco"
startActivity(intent);

Henson will automatically look up every activity that has that inject extra annotation and make this go to blank method for us. It’s automatically generated for us.

Henson will also look up to see which extras has an InjectExtra annotation and use those as setters on this intent that we’re creating. You can see that we’re passing in favorited, the description of the taco, maybe a new ingredient, and we can build that. You’ll notice I left off the tag. If we had set the tag as a nullable injected extra, then it would be null. However, since we set it as a default value, it’s going to default to taco when we start that activity.

We kick off that activity with that intent, and we’re off to the races. What if you want to use Henson without injecting any extras? All you need to do is annotate your activity with @HensonNavigable.

One small gotcha. If you’re using ProGuard, make sure you add these rules. Is that it? Do we have any more methods to implement?


grep 'TODO: implement'
=> 0 results

Looks like no. We’ve created our Tinder for tacos!

Q & A (37:32)

Q: How does Realm know to update the existing object that it has persisting in the database? Chris: You would use the ID annotation when you have your object. You can have your model have a unique ID. The same thing we might do with SQL. Then what we can do is look up that Realm object by ID, using the conditions that I shared, and then you can update that specific Realm object. Once you’ve looked it up by ID, you can update it and commit that transaction, and then it would find that one in that database.

Resources

Next Up: New Features in Realm Java

General link arrow white

About the content

This talk was delivered live in July 2016 at 360 AnDev. The video was recorded, produced, and transcribed by Realm, and is published here with the permission of the conference organizers.

Chris Guzman

Chris is an engineer at Groupon. Originally a ruby on rails developer, he made the jump into Android a year ago. He started his dev career 2 years ago after teaching himself to code. Chris loves helping others learn and leads intro to code classes as wells as co-organizes the Baltimore Junior Developer Meetup group.

4 design patterns for a RESTless mobile integration »

close