Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Applying Google's Android Architecture With ObjectBox Database

DZone's Guide to

Applying Google's Android Architecture With ObjectBox Database

Learn to create Google's Android architecture using ObjectBox instead of Room in this tutorial, where you'll see how to implement this architecture in a sample app.

· Mobile Zone
Free Resource

Download this comprehensive Mobile Testing Reference Guide to help prioritize which mobile devices and OSs to test against, brought to you in partnership with Sauce Labs.

If you haven't seen Google's architecture, you can learn more about it here. Also, if you're not familiar with ObjectBox, check out this post.

Introduction

The goal with the architecture is to end up with something like this:
The main difference is that I'll be using ObjectBox instead of Room. The architecture doesn't enforce any specific implementations though. You can always swap out the implementation details. I found ObjectBox to be one of the simplest databases, and it allows for reactive queries without depending on RxJava (though you can use RxJava if you want to).

To see the full example, you can find the GitHub repository here.

Getting Started

To start, I added two models to my project: Zoo and Animal. A Zoo has a one-to-many relation with Animals. You can see their implementations below:

@Entity
public class Zoo {

    @Id
    private long id;
    private String name;

    @Backlink
    public ToMany<Animal> animals;

    public Zoo() {
    }

    public Zoo(String name) {
        this.name = name;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
@Entity
public class Animal {

    @Id
    private long id;

    private String name;
    private String image;
    private String group;

    public ToOne<Zoo> zoo;

    public Animal() {
    }

    public Animal(String name, String image, String group) {
        this.name = name;
        this.image = image;
        this.group = group;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getGroup() {
        return group;
    }

    public void setGroup(String group) {
        this.group = group;
    }
}

Then I created a simple MainActivity (the View layer), with a RecyclerView to display a list of Zoos, and a FloatingActionButton (FAB) to add new Zoos. I also added in some mock data for this example, so the app would have some data to display. The activity's code is below:

public class MainActivity extends AppCompatActivity {

    private ZooListViewModel mViewModel;

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

        RecyclerView recyclerView = findViewById(R.id.activity_main_recyclerview);
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        ZooAdapter adapter = new ZooAdapter();
        adapter.setOnClickListener((view, position) -> {
            Intent zooIntent = new Intent(MainActivity.this, ZooActivity.class);
            zooIntent.putExtra(ZooActivity.EXTRA_ZOO_ID, adapter.getItemId(position));
            startActivity(zooIntent);
        });
        recyclerView.setAdapter(adapter);

        mViewModel = ViewModelProviders.of(this).get(ZooListViewModel.class);
        mViewModel.getZoos().observe(this, (adapter::update));

        findViewById(R.id.activity_main_fab).setOnClickListener(v -> {
            DialogFragment zooFragment = ZooFragment.newInstance();
            zooFragment.show(getSupportFragmentManager(), ZooFragment.class.getName());
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        mViewModel.refreshZoos();
    }
}

I create the ViewModel for the MainActivity which is responsible for loading a list of Zoos. The activity observes this data, and when it, changes it lets the ZooAdapter know about it. I've left out the adapter's and layouts' details as they are just regular implementations. You can view the adapter here if you would like to see it.

You can see the result below:

ViewModel

The ViewModel is essentially just responsible for communicating between the repository and view. It sends requests from the view to the repository, and returns results to the view as well. I try to make one ViewModel per view, but you could use more it if makes sense to do so. All of my ViewModels extend from a BaseViewModel class which manages the ObjectBox subscriptions:

public abstract class BaseViewModel extends ViewModel {

    private final List<DataSubscription> mSubscriptions;

    @Override
    protected void onCleared() {
        super.onCleared();
        for (DataSubscription subscription : mSubscriptions) {
            if (!subscription.isCanceled()) {
                subscription.cancel();
            }
        }
    }

    protected final void addSubscription(@NonNull DataSubscription subscription) {
        mSubscriptions.add(subscription);
    }

    public BaseViewModel() {
        mSubscriptions = new ArrayList<>();
    }
}

To really follow this type of architecture properly, I shouldn't be exposing ObjectBox to the ViewModel layer. I left it this way to keep the example simpler, but you could abstract it out with something like RxRelay.

The ViewModel which manages my MainActivity looks like this:

public class ZooListViewModel extends BaseViewModel {

    private MutableLiveData<List<Zoo>> mZoosLiveData;

    public ZooListViewModel() {
        mZoosLiveData = new MutableLiveData<>();
        DataSubscription subscription = ZooRepository.subscribeToZooList(this::refreshZoos);
        addSubscription(subscription);
    }

    private void refreshZoos(List<Zoo> zoos) {
        mZoosLiveData.postValue(zoos);
    }

    public LiveData<List<Zoo>> getZoos() {
        return mZoosLiveData;
    }

    public void refreshZoos() {
        ZooRepository.refreshZoos();
    }
}

It initializes itself with MutableLiveData to observe List, and an ObjectBox DataSubscription. This subscription monitors changes in the database, it will set the MutableLiveData whenever the data changes. Which will then notify its own observers of the change. One nice thing with LiveData is that its observers are lifecycle aware, so background data updates will only be sent when the view is active.

It also exposes a method to refresh the Zoos. This would trigger the repository to refresh the data from the source. Typically a remote server, so it would send a network request to get the latest data. As a result, the ObjectBox database gets updated and relevant subscriptions will be notified. Resulting in the LiveData in my ViewModel getting updated, passing on the result to its observers, and notifying the view layer.

Repository

The repository is responsible for passing requests from the ViewModel to the database and the network, and returning the response. I broke these layers down into API classes (to access remote APIs) and DAO classes (to access the database). You can see my implementation here:

public class ZooRepository {

    public static DataSubscription subscribeToZooList(DataObserver<List<Zoo>> observer) {
        return ZooDAO.subscribeToZooList(observer);
    }

    public static DataSubscription subscribeToZoo(DataObserver<Zoo> observer, long id, boolean singleUpdate) {
        return ZooDAO.subscribeToZoo(observer, id, singleUpdate);
    }

    public static void refreshZoo(long id) {
        ZooAPI.loadZoo(id, zooResponse -> {
            if (zooResponse != null && zooResponse.getStatus() == Response.STATUS_SUCCESS) {
                ZooParser parser = new ZooParser(zooResponse.getPayload());
                parser.parseZoo();
                Zoo zoo = parser.getZoo();
                if (zoo != null) {
                    ZooDAO.insertZoo(zoo);
                }
            }
        });
    }

    public static void refreshZoos() {
        ZooAPI.loadZoos(zoosResponse -> {
            if (zoosResponse != null && zoosResponse.getStatus() == Response.STATUS_SUCCESS) {
                ZooParser parser = new ZooParser(zoosResponse.getPayload());
                parser.parseZooList();
                List<Zoo> zoos = parser.getZooList();
                if (zoos != null) {
                    ZooDAO.insertZoos(zoos);
                }
            }
        });
    }

    public static void addZoo(Zoo newZoo, MutableLiveData<ZooUpdateResponse> liveResponse) {
        liveResponse.postValue(new ZooUpdateResponse(Response.STATUS_LOADING));
        ZooAPI.addZoo(newZoo, zooResponse -> handleZooResponse(zooResponse, liveResponse));
    }

    public static void updateZoo(Zoo zoo, MutableLiveData<ZooUpdateResponse> liveResponse) {
        liveResponse.postValue(new ZooUpdateResponse(Response.STATUS_LOADING));
        ZooAPI.updateZoo(zoo, zooResponse -> handleZooResponse(zooResponse, liveResponse));
    }

    private static void handleZooResponse(Response zooResponse, MutableLiveData<ZooUpdateResponse> liveResponse) {
        if (zooResponse != null) {
            if (zooResponse.getStatus() == Response.STATUS_SUCCESS) {
                ZooParser parser = new ZooParser(zooResponse.getPayload());
                parser.parseZoo();
                Zoo zoo = parser.getZoo();
                if (zoo != null) {
                    ZooDAO.insertZoo(zoo);
                }
            }

            liveResponse.postValue(new ZooUpdateResponse(zooResponse.getStatus()));
        }
    }
}

I keep most of the logic in the repository layer. The DAO and API layers only have one responsibility. For the DAO it's interacting with the database, and for the API it's interacting with the remote API. They are either passing data on, or returning it, they don't manipulate it themselves. So when the repository gets a response from the API, it's responsible for parsing that response and subsequently sending it to the DAO to update the database.

If the repository is given an Observer or LiveData, it sets its value on completion to pass the response back to the ViewModel. These response objects can contain extra information like status codes (success, failure, etc.) and error messages. Since the data models won't contain this type of information. The implementation of these response objects really depends on what kind of information the ViewModel needs.

Parsing

To prevent the repository from becoming bloated, I split the response parsing logic into its own set of classes. These parsing classes just take a response String (likely JSON), and convert it into the appropriate model(s). For parsing Zoo responses my class looks like this:

public class ZooParser {

    private String mResponse;
    private Zoo mZoo;
    private List<Zoo> mZooList;
    private List<Animal> mAnimalList;
    private Gson mGson;

    public ZooParser(String response) {
        mResponse = response;
        mGson = new Gson();
    }

    public void parseZooList() {
        if (mResponse != null) {
            Zoo[] zoos = mGson.fromJson(mResponse, Zoo[].class);
            mZooList = Arrays.asList(zoos);
        }
    }

    public void parseZoo() {
        if (mResponse != null) {
            mZoo = mGson.fromJson(mResponse, Zoo.class);
        }
    }

    public Zoo getZoo() {
        return mZoo;
    }

    public List<Zoo> getZooList() {
        return mZooList;
    }

    public List<Animal> getAnimalList() {
        return mAnimalList;
    }
}

You may notice I included a list of Animals in the parser - this is because it's possible that a response for a Zoo could contain Animals as well. Although I didn't actually implement any such responses in this example. The idea here is that a Parser class isn't directly mapping JSON objects to my data models. It parses the entire response, which may include multiple data models or specific keys that don't belong to models (like page count).

DAO

This layer is responsible for interacting with the database. I would create DAO classes for each model, like my ZooDAO:

public class ZooDAO {

    private static Box<Zoo> getZooBox() {
        BoxStore boxStore = App.getBoxStore();
        return boxStore.boxFor(Zoo.class);
    }

    public static DataSubscription subscribeToZooList(DataObserver<List<Zoo>> observer) {
        return getZooBox().query().build().subscribe().on(AndroidScheduler.mainThread()).observer(observer);
    }

    public static DataSubscription subscribeToZoo(DataObserver<Zoo> observer, long id, boolean singleUpdate) {
        SubscriptionBuilder<Zoo> builder = getZooBox().query().eager(Zoo_.animals).equal(Zoo_.id, id).build().subscribe().transform(list -> {
            if (list.size() == 0) {
                return null;
            } else {
                return list.get(0);
            }
        }).on(AndroidScheduler.mainThread());

        if (singleUpdate) {
            builder.single();
        }
        return builder.observer(observer);
    }

    public static void insertZoo(Zoo zoo) {
        getZooBox().put(zoo);
    }

    public static void insertZoos(Collection<Zoo> zoos) {
        getZooBox().put(zoos);
    }
}

Here you can see ObjectBox being accessed directly. It performs any required database operations, and builds query subscriptions. These queries can be observed in the ViewModel, which notifies the LiveData of any data updates.

API

The API layer is used to make requests to the network and pass those responses back to the repository. The implementation in this example is mocked out, you can see the source here. This could use any type of network requests, you could use Retrofit for example.

The repository observes the response from the API and handles them. Every API request will create a Response object:

public class Response {

    public static final int STATUS_LOADING = 0, STATUS_SUCCESS = 1, STATUS_FAIL = 2;

    @Retention(SOURCE)
    @IntDef({STATUS_LOADING, STATUS_SUCCESS, STATUS_FAIL})
    @interface Status {
    }

    private final int mStatus;
    private String mPayload;

    public Response(@Status int status, String payload) {
        mStatus = status;
        mPayload = payload;
    }

    @Status
    public int getStatus() {
        return mStatus;
    }

    public String getPayload() {
        return mPayload;
    }
}

This is the relevant information that the repository needs - the response payload and status. More statuses could be added, for example for specific error statuses like authentication failure.

Bringing It All Together

In this example, I have my MainActivity observing the Zoos LiveData. It will be notified whenever this value is set. For example, if I add a new Zoo, it will automatically refresh the view. This is because I have set up a DataSubscription with ObjectBox to observe changes. You can see this in action here:

You can find the source code for this DialogFragment here. The important piece of this class is that it's observing the response here:

...

        ZooFragmentViewModel viewModel = ViewModelProviders.of(this).get(ZooFragmentViewModel.class);
        viewModel.getZooUpdateResponse().observe(this, response -> {
            if (response != null) {
                switch (response.getStatus()) {
                    case Response.STATUS_LOADING:
                        showProgressBar(true);
                        break;
                    case Response.STATUS_SUCCESS:
                        dismiss();
                        break;
                    case Response.STATUS_FAIL:
                        showProgressBar(false);
                        Toast.makeText(getContext(), response.getErrorMessage(), Toast.LENGTH_SHORT).show();
                        break;
                }
            }
        });

...

When the save action is triggered in the ViewModel, it sets the LiveData's response with the loading status. Then when it succeeds or fails, it sets that result back as well. Once it returns back to MainActivity, you can see the new Zoo was added without having to explicitly trigger a refresh.

Wrap-up

To see the full example, you can find the GitHub repository here. But, you can apply what I have explained here to any new views/models/repositories.

I believe this architecture is a bit easier to understand and apply than the fully Clean Architecture. But, the same principles apply - like isolating unique layers and giving them a single responsibility. I do not consider what I have presented here to be complete, it is meant to be a starting point. You can use it as is, but you should be thinking about how you can build and improve on it. Moving towards something like Buffer's Clean Architecture Boilerplate is the goal. But, it can be overwhelming to grasp all of these concepts without a solid base to stand on.

Also, in an effort to make it easier to grasp, I tried to keep dependencies to a minimum. I did not include things like Retrofit, OkHttp, Butterknife, RxJava, Dagger, etc. You can add whatever libraries you like to use, you can swap out ObjectBox too. Though I think it works well with this approach.

Let me know what you think of this approach. Also if you have any comments, questions, or suggestions please post them.

Analysts agree that a mix of emulators/simulators and real devices are necessary to optimize your mobile app testing - learn more in this white paper, brought to you in partnership with Sauce Labs.

Topics:
mobile ,mobile app development ,android ,objectbox

Published at DZone with permission of Pierce Zaifman, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}