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

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.
/**
* 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 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);
}
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
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();
}
}
Fragments
Recycler Adapters
Page Adapters
Bottom Sheets
Custom Views
Last updated