Application Core

Documentation about the core application architecture

Overview

The AniTrend architecture is built around a structural design pattern MVVMP with some customisation that best suit the use case for this application, the architecture is something I came up with and may violate some pattern standards but as we all know these are guidelines, not commandments.

The goal for the application architecture is to keep components independent and self managed or self aware of their own state to promote decoupling. All communication between non attached classes is achieved by EventBus

Activity & Fragment Lifecycle

The app makes use of an application class called App which can be found in com.mxt.anitrend.App and servers as the initial application starting point. It is responsible for configuring things like the database or any other application related configurations that will be used throughout application instance

Most of the core building blocks of the android classes have extended to base classes that offer the most most commonly used functions in any android application, making use of generics and inheritance allows easier code reuse and extending.

View Model

The ViewModel class is designed to store and manage UI-related data in a life cycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.

public class ViewModelBase<T> extends ViewModel implements RetroCallback<T>

Looking at the class signature you'll notice that the class is generic and implements RetroCallback which is an extension interface for Retrofit callbacks, and the shared generic type represents our model type that will be used by any of your activities or fragments.

Press Ctrl and hover your cursor over a class name or property to navigate to it from android studio, you can also hit Shift twice to search for any file in the current project

/**
* Created by max on 2017/10/14.
* Annotated extension of retrofit callbacks
*/
public interface RetroCallback<T> extends Callback<T> {
/**
* Invoked for a received HTTP response.
* <p>
* Note: An HTTP response may still indicate an application-level failure such as a 404 or 500.
* Call {@link Response#isSuccessful()} to determine if the response indicates success.
*
* @param call the origination requesting object
* @param response the response from the network
*/
@Override
void onResponse(@NonNull Call<T> call, @NonNull Response<T> response);
/**
* Invoked when a network exception occurred talking to the server or when an unexpected
* exception occurred creating the request or processing the response.
*
* @param call the origination requesting object
* @param throwable contains information about the error
*/
@Override
void onFailure(@NonNull Call<T> call, @NonNull Throwable throwable);
}

Now that we've got that out of the way, let's talk about responsibilities of our view model, in this case our view model will be used by multiple Fragments and Activities by simply calling the setViewModel(true) in your onCreate method

The setViewModel(true) method is available in the ActivityBase & FragmentBase classes and it creates a view model and sets all the subscription methods and callbacks for you. We'll get to this later

The view model handles dispatching of network requests and the corresponding responses, you'll see that the ActivityBase class implements Observer<M> which is how our view model notifies either of these about the data model changes

/**
* A simple callback that can receive from {@link LiveData}.
*
* @param <T> The type of the parameter
*
* @see LiveData LiveData - for a usage description.
*/
public interface Observer<T> {
/**
* Called when the data is changed.
* @param t The new data
*/
void onChanged(@Nullable T t);
}

ViewModelBase uses Bundle instead of plain objects, which is used by our RequestHandler that handles all our network requests (it is also a multi threaded execution task).

Presenters

Since we're using both view models and presenters, we've moved all our models into our view model and only left the presenters with the task of providing some core objects to our activities, fragments and views. Every presenter that needs to provide extra functionality is derived from the base presenter class:

com.mxt.anitrend.base.custom.presenter.CommonPresenter

The common presenter exposes a couple of objects to any classes that use it and the class itself is mainly responsible to binding and unbinding Shared Preferences Change Listeners, exposing the Database Helper

The presenter also extends a class called RecyclerScrollListener that represents a custom OnScrollListener for RecyclerView which allow us to pre-fetch data when user reaches the bottom in the list.

Activities

A common super Activity class has been provided for general consumption found in com.mxt.anitrend.base.custom.activity This is a generic abstract class that extends AppCompatActivity which should be used on any activity in the this application.

/**
* Created by max on 2017/06/09.
* ActivityBase <M type of data model, P extends CommonPresenter>
*/
public abstract class ActivityBase<M, P extends CommonPresenter>
extends AppCompatActivity implements Observer<M>, CommonPresenter.AbstractPresenter<P>,
ResponseCallback, MaterialSearchView.SearchViewListener, MaterialSearchView.OnQueryTextListener {
}

The ActivityBase contains a lot of helpful methods that are commonly used in this application, it handles many changes such as:

  • Theme selection based on what the user chose ActivityBase#configureActivity()

  • Deep Linking identification vial the IntentBundleUtil

  • Configuring the search view if the current child activity is using one

  • Requesting application permissions

  • Getting the set presenter and view model

  • Showing the bottom sheet

Example Usage

Let's look at a basic example from the splash activity com.mxt.anitrend.view.activity.index.SplashActivity which extends from ActivityBase<VersionBase, BasePresenter>

The generic parameters passed are the ActivityBase<Model, Presenter> for the current class, the model will be used by the View Model as it handles requests, it needs to know the type to expect and use it on the onChanged(M model) method. The given presenter type is utilised when using setPresenter(new BasePresenter(this)); and when using getPresnter()

public class SplashActivity extends ActivityBase<VersionBase, BasePresenter> {
protected @BindView(R.id.preview_credits)
WideImageView giphyCitation;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
ButterKnife.bind(this);
setPresenter(new BasePresenter(this));
setViewModel(true);
}
@Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
giphyCitation.setImageResource(!CompatUtil.isLightTheme(this) ? R.drawable.powered_by_giphy_light : R.drawable.powered_by_giphy_dark);
onActivityReady();
}
/**
* Make decisions, check for permissions or fire background threads from this method
* N.B. Must be called after onPostCreate
*/
@Override
protected void onActivityReady() {
getPresenter().checkGenresAndTags(this);
getPresenter().checkValidAuth();
makeRequest();
}
@Override
protected void updateUI() {
if(isAlive()) {
boolean freshInstall = getPresenter().getApplicationPref().isFreshInstall();
Intent intent = new Intent(SplashActivity.this, freshInstall?WelcomeActivity.class:MainActivity.class);
startActivity(intent);
finish();
}
}
@Override
protected void makeRequest() {
VersionBase versionBase = getPresenter().getDatabase().getRemoteVersion();
// How frequent should the application check for updates on startup
if(versionBase == null || DateUtil.timeDifferenceSatisfied(KeyUtil.TIME_UNIT_HOURS, versionBase.getLastChecked(), 4)) {
getViewModel().getParams().putString(KeyUtil.arg_branch_name, getPresenter().getApplicationPref().getUpdateChannel());
getViewModel().requestData(KeyUtil.UPDATE_CHECKER_REQ, getApplicationContext());
}
else
updateUI();
}
/**
* Called when the model state is changed.
*
* @param model The new data
*/
@Override
public void onChanged(@Nullable VersionBase model) {
super.onChanged(model);
if(model != null)
getPresenter().getDatabase().saveRemoteVersion(model);
updateUI();
}
@Override
public void showError(String error) {
updateUI();
}
@Override
public void showEmpty(String message) {
updateUI();
}
}

Note that we are using Butter Knife to bind our view ids to our view objects. Also looking at the ActivityBase<VersionBase, BasePresenter> has set VersionBase as it's model for the class and BasePresenter as our presenter type since ActivityBase has a P getPresnter() and a void setPresenter(P presenter) method

Fragments

Recycler Adapters

Page Adapters

Bottom Sheets

Custom Views