Skip to content

Instantly share code, notes, and snippets.

@codeprogression
Last active October 14, 2017 14:51
Show Gist options
  • Save codeprogression/02fc981719876eb3623b to your computer and use it in GitHub Desktop.
Save codeprogression/02fc981719876eb3623b to your computer and use it in GitHub Desktop.
Data Binding Library Presenter Pattern

An updated example is available here: https://github.com/codeprogression/android-mvpb

The updated example does not use the BoundRelativeLayout. It also moves the view state saving to the view rather than in the presenter. The presenter should not have any reference to the Android framework.

<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.example.observablebinding.ui.MainActivityView.ViewModel" />
<variable
name="listener"
type="com.example.observablebinding.ui.MainActivityView" />
</data>
<com.example.observablebinding.ui.MainActivityView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main_activity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:saveEnabled="true"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:numberText="@{viewModel.number}" />
<Button
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{listener.add}"
android:text="+" />
</com.example.observablebinding.ui.MainActivityView>
</layout>
package com.example.observablebinding.core.ui;
import android.os.Parcelable;
import android.view.View;
public abstract class BasePresenter<VM> {
protected VM viewModel;
/**
* Call in {@code View.onAttachedToWindow}
* @param viewModel
*/
public void onAttach(VM viewModel){
if (this.viewModel == null) {
this.viewModel = viewModel;
load();
}
}
/**
* Call in {@code View.onDetachedFromWindow}
*/
public void onDetach(){
unload();
}
/**
* Used to initialize the view. Called by default when attached to view.
*
* Start any data fetching operations here.
*/
public abstract void load();
/**
* Used to unload the view. Called when presenter is detached from view.
*
* Stop any data fetching operations here
*/
protected abstract void unload();
/**
* To enable saved state add {@code android:saveEnabled="true"} to layout root
* @param superState
* @return
*/
public View.BaseSavedState saveState(Parcelable superState){
return null;
}
public void restoreState(VM viewModel, View.BaseSavedState savedState) {
this.viewModel = viewModel;
}
}
package com.example.observablebinding.core.ui;
import android.content.Context;
import android.databinding.DataBindingUtil;
import android.databinding.Observable;
import android.databinding.ViewDataBinding;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
import com.example.observablebinding.BR;
public abstract class BoundRelativeLayout<T extends BasePresenter, VM extends Observable>
extends RelativeLayout {
protected T presenter;
protected VM viewModel;
public BoundRelativeLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public abstract VM getViewModel();
@Override
protected void onFinishInflate() {
super.onFinishInflate();
presenter = getPresenter();
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
ViewDataBinding binding = DataBindingUtil.bind(this);
binding.setVariable(BR.listener, this);
binding.setVariable(BR.viewModel, getViewModel());
if (presenter == null) return;
presenter.onAttach(getViewModel());
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (presenter == null) return;
presenter.onDetach();
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
BaseSavedState state = presenter == null ? null : presenter.saveState(superState);
return state != null ? state : superState;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if(!(state instanceof BaseSavedState)) {
super.onRestoreInstanceState(state);
return;
}
BaseSavedState ss = (BaseSavedState)state;
super.onRestoreInstanceState(ss.getSuperState());
//end
if (presenter == null) return;
presenter.restoreState(getViewModel(), ss);
}
}
package com.example.observablebinding.ui;
import android.os.Bundle;
import android.os.PersistableBundle;
import android.support.v7.app.AppCompatActivity;
import com.example.observablebinding.R;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
}
package com.example.observablebinding.ui;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.View;
import com.example.observablebinding.core.ui.BasePresenter;
public class MainActivityPresenter
extends BasePresenter<MainActivityView.ViewModel> {
@Override
public void load() {
if (viewModel.number.get() < 10) {
viewModel.number.set(10);
}
}
@Override
protected void unload() {
}
public void add(){
int number = viewModel.number.get() + 1;
viewModel.number.set(number);
}
// region ViewState
@Override
public View.BaseSavedState saveState(Parcelable superState) {
SavedState savedState = new SavedState(superState);
savedState.number = viewModel.number.get();
return savedState;
}
@Override
public void restoreState(MainActivityView.ViewModel viewModel, View.BaseSavedState savedState) {
super.restoreState(viewModel, savedState);
this.viewModel.number.set(((SavedState) savedState).number);
}
static class SavedState extends View.BaseSavedState {
int number;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
this.number = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(this.number);
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
// endregion
}
package com.example.observablebinding.ui;
import android.content.Context;
import android.databinding.BaseObservable;
import android.databinding.ObservableInt;
import android.util.AttributeSet;
import android.view.View;
import com.example.observablebinding.core.ui.BoundLinearLayout;
public class MainActivityView
extends BoundLinearLayout<MainActivityPresenter, MainActivityView.ViewModel> {
private ViewModel viewModel;
/*
* BTW, I prefer to inject the presenter using Dagger 2 versus creating the dependency here.
*
* {@code MainActivity.component().inject(this); }
*/
public MainActivityView(Context context, AttributeSet attrs) {
super(context, attrs);
presenter = new MainActivityPresenter();
}
@Override
public ViewModel getViewModel() {
if (viewModel == null) {
viewModel = new ViewModel();
}
return viewModel;
}
public void add(View v) {
// int number = viewModel.number.get() + 1;
// viewModel.number.set(number);
// Use presenter for domain operations
presenter.add();
}
public static class ViewModel extends BaseObservable {
public ObservableInt number = new ObservableInt();
}
}
package com.example.observablebinding.core.ui;
import android.databinding.BindingAdapter;
import android.widget.TextView;
import org.w3c.dom.Text;
public final class TextViewDataBinding {
@BindingAdapter("numberText")
public static void setNumberText(TextView view, int number){
view.setText(String.valueOf(number));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment