Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to use MVVM pattern in Android

How to use MVVM pattern in Android

Presented during Droidcon.de 2015.

MVVM on Android using MVVMCross, Robobinding, ngAndroid, Bindroid and official Google Android Data Binding library.

Radek Piekarz

June 04, 2015
Tweet

More Decks by Radek Piekarz

Other Decks in Programming

Transcript

  1. Agenda • The need for better tools • How can

    we make things better • MVP & MVVM - design patterns you should use • MVVM on Android – different approaches • Pros & Cons • Summary • Q&A
  2. Butterknife public class MyActivity extends Activity { @InjectView(R.id.button) Button button;

    @OnClick(R.id.button) void onButtonClicked() { // some black magic } //... }
  3. Android Annotations @EActivity(R.layout.main_activity) public class MyActivity extends Activity { @ViewById(R.id.button)

    Button button; @Click(R.id.button) public void onButtonClicked() { //... } //... } Android Annotations
  4. NO

  5. Model View Presenter • The model is an interface defining

    the data to be displayed • The view is a passive interface that displays data and routes user commands (events) to the presenter to act upon that data. • The presenter acts upon the model and the view. It retrieves data from repositories (the model), and formats it for display in the view. Passive View Model Presenter user events updates model updates view state-change event
  6. Model View Presenter on Android #1 public interface MyView {

    void hideLoading(); void showLoading(); void renderItems(final Collection<Item> items); } Let’s define view interface
  7. roid #2 public class MyViewPresenter extends Presenter { private MyView

    view; public void setView(MyView view) { this.view = view; } @Override public void initialize() { view.showLoading(); //do some heavy operations view.renderItems(...); view.hideLoading(); } Implement presenter
  8. roid #2 Implement our view public class MyViewImpl extends Activity

    implements MyView { private MyViewPresenter presenter; @InjectView(R.id.progress_bar) ProgressBar progressBar; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.home_view); ButterKnife.inject(this); this.presenter = new MyViewPresenter(this); this.presenter.initialize(); } //TODO implement rest of MyView methods
  9. MVVMCross - Activity (View) namespace App.Views { [Activity(Label = "MyView")]

    public class MyView : MvxActivity<MyViewModel> { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.LunchDetails); } } }
  10. MVVMCross - Activity (View) namespace App.Views { [Activity(Label = "MyView")]

    public class MyView : MvxActivity<MyViewModel> { protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); SetContentView(Resource.Layout.LunchDetails); } } }
  11. MVVMCross - Activity (Layout) <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res-auto"

    android:layout_width="match_parent" android:layout_height="match_parent"> <Mvx.MvxListView ... local:MvxBind="ItemsSource Items;ItemClick ItemSelectedCommand" local:MvxItemTemplate="@layout/listitem/> <Button ... android:text="Add" local:MvxBind="Click AddCommand" /> </FrameLayout>
  12. MVVMCross - Activity (Layout) <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:local="http://schemas.android.com/apk/res-auto"

    android:layout_width="match_parent" android:layout_height="match_parent"> <Mvx.MvxListView ... local:MvxBind="ItemsSource Items;ItemClick ItemSelectedCommand" local:MvxItemTemplate="@layout/listitem/> <Button ... android:text="Add" local:MvxBind="Click AddCommand" /> </FrameLayout>
  13. MVVMCross - ViewModel public class MyViewModel : MvxViewModel { public

    IMvxCommand AddNewCommand { get { return new MvxCommand(Open); } } private List<MyEntity> _items; public List<MyEntity> Items { get { return _items; } set { _items = value; RaisePropertyChanged(() => Items); } } // rest of code
  14. MVVMCross - ViewModel public class MyViewModel : MvxViewModel { public

    IMvxCommand AddNewCommand { get { return new MvxCommand(Open); } } private List<MyEntity> _items; public List<MyEntity> Items { get { return _items; } set { _items = value; RaisePropertyChanged(() => Items); } } // rest of code
  15. MVVMCross • Pros • TwoWay binding • Easy to extend

    • Supports Lists • Reuse ViewModel across iOS/Android/Windows Phone • Used by many developers, many apps published to Google Play/AppStore etc. • True MVVM similar to WPF • Cons • Only for Xamarin/C# • May need some additional work in order to make it work with AppCompat Library
  16. RoboBinding - setup #2 apply plugin: 'com.android.application' apply plugin: 'com.neenbedankt.android-apt'

    apt("org.robobinding:codegen:$robobindingVersion") { exclude group: 'com.google.android', module: 'android' } compile("org.robobinding:robobinding:$robobindingVersion") { exclude group: 'com.google.android', module: 'android' }
  17. Android & RoboBinding - hello world @PresentationModel public class MainViewViewModel

    implements HasPresentationModelChangeSupport { private PresentationModelChangeSupport changeSupport; private String name; public MainViewViewModel() { changeSupport = new PresentationModelChangeSupport(this); } public String getHello() { return name + ": hello Android MVVM(Presentation Model)!"; } public String getName()..... public void setName(String name) …. public void sayHello() { changeSupport.firePropertyChange("hello"); } @Override public PresentationModelChangeSupport getPresentationModelChangeSupport() { return changeSupport;
  18. Android & RoboBinding - hello world @PresentationModel public class MainViewViewModel

    implements HasPresentationModelChangeSupport { private PresentationModelChangeSupport changeSupport; private String name; public MainViewViewModel() { changeSupport = new PresentationModelChangeSupport(this); } public String getHello() { return name + ": hello Android MVVM(Presentation Model)!"; } public String getName()..... public void setName(String name) …. public void sayHello() { changeSupport.firePropertyChange("hello"); } @Override public PresentationModelChangeSupport getPresentationModelChangeSupport() { return changeSupport;
  19. Android & Robobinding- hello world public class MainActivity extends Activity

    { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MainViewViewModel presentationModel = new MainViewViewModel(); View rootView = Binders.inflateAndBindWithoutPreInitializingViews(this, R.layout.activity_main, presentationModel); setContentView(rootView); } }
  20. Android & Robobinding - hello world public class MainActivity extends

    Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MainViewViewModel presentationModel = new MainViewViewModel(); View rootView = Binders.inflateAndBindWithoutPreInitializingViews(this, R.layout.activity_main, presentationModel); setContentView(rootView); } }
  21. Android & Robobinding - hello world <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:bind="http://robobinding.org/android"

    tools:ignore="MissingPrefix" android:orientation="vertical"> <TextView …….. bind:text="{hello}"/> <EditText ……. bind:text="${name}"/> <Button …. android:text="Say Hello" bind:onClick="sayHello"/>
  22. Android & Robobinding - hello world <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:bind="http://robobinding.org/android"

    tools:ignore="MissingPrefix" android:orientation="vertical"> <TextView …….. bind:text="{hello}"/> <EditText ……. bind:text="${name}"/> <Button …. android:text="Say Hello" bind:onClick="sayHello"/>
  23. RoboBinding • Pros • TwoWay binding • Code generation •

    Easy to extend • Supports Lists • Cons • No support for RecyclerView • Problems with AppCompat library • No support for Lists with two and more types of item layout https://github.com/radzio/RoboBindingDemo
  24. ngAndroid @NgScope public class NgModelFragment extends Fragment { private final

    NgAndroid ng = NgAndroid.getInstance(); @NgModel NgMod mod; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return ng.inflate(this, inflater, R.layout.activity_ng_model, container, false); } }
  25. ngAndroid @NgScope public class NgModelFragment extends Fragment { private final

    NgAndroid ng = NgAndroid.getInstance(); @NgModel NgMod mod; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return ng.inflate(this, inflater, R.layout.activity_ng_model, container, false); } }
  26. ngAndroid public class NgMod { private String str; public String

    getStr() { return str; } public void setStr(String str) { this.str = str; } }
  27. • Pros • TwoWay binding • Code generation • Cons

    • No support for lists / RecyclerView • It is NOT a production ready library ngAndroid
  28. Bindroid public class ViewModel { private TrackableField<String> stringValue = new

    TrackableField<String>("Hello, world!"); private TrackableField<TrackableCollection<Date>> dates = new TrackableField<TrackableCollection<Date>>(); public String getStringValue() { return stringValue.get(); } public void setStringValue(String value) { stringValue.set(value); } public TrackableCollection<Date> getDates() { return dates.get(); }
  29. Bindroid public class ViewModel { private TrackableField<String> stringValue = new

    TrackableField<String>("Hello, world!"); private TrackableField<TrackableCollection<Date>> dates = new TrackableField<TrackableCollection<Date>>(); public String getStringValue() { return stringValue.get(); } public void setStringValue(String value) { stringValue.set(value); } public TrackableCollection<Date> getDates() { return dates.get(); }
  30. Bindroid @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewModel

    model = new ViewModel(); UiBinder.bind(new EditTextTextProperty(this.findViewById(R.id.TextField)), model, "StringValue", BindingMode.TWO_WAY); UiBinder.bind(this, R.id.ListView, "Adapter", model, "Dates", BindingMode.ONE_WAY, new AdapterConverter(DateView.class)); }
  31. Bindroid @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewModel

    model = new ViewModel(); UiBinder.bind(new EditTextTextProperty(this.findViewById(R.id.TextField)), model, "StringValue", BindingMode.TWO_WAY); UiBinder.bind(this, R.id.ListView, "Adapter", model, "Dates", BindingMode.ONE_WAY, new AdapterConverter(DateView.class)); }
  32. Bindroid • Pros • TwoWay binding • Works with AppCompat

    library • Converters support • Cons • No code generation • No compile-time check • Too much code for binding
  33. Android Data Binding Library • Announced at Google IO 15

    • Created by Google • Currently available as beta
  34. Android Data Binding Library – how to start classpath "com.android.tools.build:gradle:1.2.3"

    classpath "com.android.databinding:dataBinder:1.0-rc0" apply plugin: ‘com.android.application' apply plugin: 'com.android.databinding'
  35. Android Data Binding Library – how to start classpath "com.android.tools.build:gradle:1.2.3"

    classpath "com.android.databinding:dataBinder:1.0-rc0" apply plugin: ‘com.android.application' apply plugin: 'com.android.databinding'
  36. Android Data Binding Library – how to start <layout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="usersViewModel" type="net.droidlabs.mvvmdemo.viewmodel.UsersViewModel"/> <variable name="view" type="net.droidlabs.mvvmdemo.view.UsersView"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/activity_users_recycler" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:items="@{usersViewModel.users}" app:itemViewBinder="@{view.itemViewBinder}" />
  37. Android Data Binding Library – how to start <layout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="usersViewModel" type="net.droidlabs.mvvmdemo.viewmodel.UsersViewModel"/> <variable name="view" type="net.droidlabs.mvvmdemo.view.UsersView"/> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.v7.widget.RecyclerView android:id="@+id/activity_users_recycler" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" app:items="@{usersViewModel.users}" app:itemViewBinder="@{view.itemViewBinder}" />
  38. Android Data Binding Library – how to start public class

    UsersViewModel extends BaseObservable { @Bindable public ObservableArrayList<UserViewModel> users; public UsersViewModel() { this.users = new ObservableArrayList<>(); } public void addUser(String name, String surname) { this.users.add(new UserViewModel(new User(name, surname))); } }
  39. Android Data Binding Library – how to start public class

    UsersViewModel extends BaseObservable { @Bindable public ObservableArrayList<UserViewModel> users; public UsersViewModel() { this.users = new ObservableArrayList<>(); } public void addUser(String name, String surname) { this.users.add(new UserViewModel(new User(name, surname))); } }
  40. Android Data Binding Library – how to start @Override protected

    void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); usersViewModel = new UsersViewModel(); usersViewModel.users.add(new SuperUserViewModel(new User("Android", "Dev"))); binding = DataBindingUtil.setContentView(this, R.layout.users_view); binding.setUsersViewModel(usersViewModel); binding.setView(this); binding.activityUsersRecycler.setLayoutManager(new LinearLayoutManager(this)); }
  41. Android Data Binding Library – how to start @Override protected

    void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); usersViewModel = new UsersViewModel(); usersViewModel.users.add(new SuperUserViewModel(new User("Android", "Dev"))); binding = DataBindingUtil.setContentView(this, R.layout.users_view); binding.setUsersViewModel(usersViewModel); binding.setView(this); binding.activityUsersRecycler.setLayoutManager(new LinearLayoutManager(this)); }
  42. Android Data Binding Library – there is more! • Expression

    language android:text='@{String.format("Hello %s",viewModel.field )}‚ • Imports • Support for resources android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}„ • Built in ButterKnife functionality binding.myButton.setOnClickListener(new …); • Ability to create custom bindings • And more!
  43. Android Data Binding Library • Pros • Official library from

    Google  • Code generation • Compile time check • Easy to use and extend • Expression language! • New standard • Cons • No TwoWay binding (yet) • Currently NO IDE support • A lot of false positive errors in Android Studio, but code is compiling and running
  44. Takeaway #1 • Learn and use MVP pattern • Get

    familiar with MVVM and try Android Data Binding Library • Write Unit Tests for your Presenters and ViewModels 