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

HS Android - Real MVVM with DataBinding and Realm

HS Android - Real MVVM with DataBinding and Realm

Eugene Mager, Android developer, Trinity Ukraine

Я займаюся Android розробкою достатньо довго, щоб зрозуміти, що без правильного проектування структури проекту неможливо отримати якісний продукт.
Android DataBinding пропонує не тільки пришвидшення розробки, але і непоганий базис для побудови легкої та простої архітектури. А поєднавши DataBinding з Realm можна досягнути майже ідеального MVVM.

GDG Cherkasy

June 03, 2017
Tweet

More Decks by GDG Cherkasy

Other Decks in Programming

Transcript

  1. XML -- <layout> </layout> ViewModel Event Handlers RealmObjects DAO HTTP

    requests WebSocket Push notifications Services/ threads onChange onChange transaction
  2. XML is a View <?xml version="1.0" encoding="utf-8"?> <layout> <data> <variable

    name="viewModel" type="com.example.mvvm.weather.WeatherViewModel"/> </data> <LinearLayout > </LinearLayout> </layout>
  3. @BindingAdapter("childView") public static void bindSetChildViewToFrameLayout(FrameLayout view, View childView) { if

    (childView != null) { view.removeAllViews(); view.removeAllViewsInLayout(); view.addView(childView); } } @BindingConversion public static int convertBooleanToVisibility(boolean visible) { return visible ? View.VISIBLE : View.GONE; }
  4. Event listeners via automatic setters is easy: public class WeatherViewModel

    extends BaseObservable implements View.OnTouchListener { @Override public boolean onTouch(View v, MotionEvent event) { //some code return true; } } <LinearLayout app:onTouchListener="@{viewModel}">
  5. Event listeners via automatic setters is easy: public class WeatherViewModel

    extends BaseObservable { public boolean onTouch() { return true; } } <LinearLayout app:onTouchListener="@{() -> viewModel.onTouch()}">
  6. Model layer @RealmClass public class WeatherRealm extends RealmObject{ @PrimaryKey private

    String city; private double temperature; private String weatherConditions; private String icon; public WeatherRealm() {} //Getters and setters }
  7. public String getTemperatureIn(String s) { WeatherRealm w = weatherRealm.where().equalTo("city", s).findFirst();

    return w == null || !w.isValid() ? "--" : String.valueOf(w.getTemperature()); } public String getWeatherConditionsIn(String s) { WeatherRealm w = weatherRealm.where().equalTo("city", s).findFirst(); return w == null || !w.isValid() ? "--" : w.getWeatherConditions(); }
  8. Model layer Main things to remember: • Object isValid •

    Realm instance isClosed • Create empty constructor • Realm instance on UI and non UI thread Realm.getGlobalInstanceCount(configutarion) Realm.getLocalInstanceCount(configutarion)
  9. ViewModel layer public class WeatherViewModel extends BaseObservable implements OrderedRealmCollectionChangeListener <RealmResults<WeatherRealm>>

    { public WeatherViewModel() { weatherDAO = new WeatherDAO(); weatherCaller = new WeatherCaller(weatherDAO); } }
  10. ViewModel layer public void onResume() { weatherDAO.addChangeListener(this); } public void

    onPause() { weatherDAO.clearListeners(); } @Override public void onChange( RealmResults<WeatherRealm> weatherRealms, OrderedCollectionChangeSet changeSet) { notifyPropertyChanged(BR._all); }
  11. @Override public void onChange( RealmResults<WeatherRealm> weatherRealms, OrderedCollectionChangeSet changeSet) { notifyPropertyChanged(BR._all);

    } @Override public void onChange(CityInfoRealm cityInfo) { notifyPropertyChanged(BR.toolbarTitle); notifyPropertyChanged(BR.conditionIcon); }
  12. <TextView android:text="@{viewModel.temperature}"/> <TextView android:text="@{viewModel.weatherConditions}"/> <TextView android:text="@{viewModel.city}"/> @Bindable public String getTemperature()

    { return weatherDAO.getTemperatureIn(searchCity.get());} @Bindable public String getWeatherConditions() { return weatherDAO.getWeatherConditionsIn(searchCity.get());} @Bindable public String getCity() { return searchCity.get();}
  13. @BindingAdapter("xCoord") public static void setViewXCoord(View view, float x) { if

    (x != view.getX() && x != DO_NOT_SET_COORD) { view.setX(x); } } @InverseBindingAdapter(attribute = "yCoord") public static float getViewYCoord(View view) { return view.getY(); }
  14. @BindingAdapter({"xCoordAttrChanged"}) public static void setXCoordinateListener(View view, final InverseBindingListener coordChange) {

    if (coordChange == null) { view.getViewTreeObserver().addOnGlobalLayoutListener(null); } else { view.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { coordChange.onChange(); } }); } }
  15. Network Layer public void getWeatherForCity(final String city) { WeatherService service

    = RetrofitService.get().create(WeatherService.class); service.getWeather(city).enqueue(this); } @Override public void onResponse( Call<Weather> call, Response<Weather> response) { if (response.isSuccessful()) weatherDAO.saveCityWeatherToRealm(response.body()); }
  16. public void saveCityWeatherToRealm(final Weather body) { realmInstane.executeTransactionAsync(new Realm.Transaction() { @Override

    public void execute(Realm realm) { WeatherRealm weatherRealm = new WeatherRealm(body); realm.insertOrUpdate(weatherRealm); } }); }
  17. new OrderedRealmCollectionChangeListener() { @Override public void onChange(Object collection, OrderedCollectionChangeSet changeSet)

    { // For deletions, the adapter has to be notified in reverse order. OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges(); OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges(); if (!updateOnModification) { return; } OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges(); } };
  18. @Override protected void rangeChanged(int startIndex, int length) { ProgressRealmObject object

    = null; for (int i = startIndex; i < startIndex+length; i++) { object = getItem(i); if (object != null && object.isValid()) { if (object.getProgress() >= 0 && object.getProgress() <= 100) { viewModels.get(object.getId()) .notifyPropertyChanged(BR.progress); }}}} public class ItemViewModel extends BaseObservable { @Bindable public int getProgress() { if (object == null || !object.isValid()) {return 0;} return object.getProgress();}
  19. Недоліки • Постійно треба пам’ятати, що RealmObject може ще не

    існувати, або бути вже not valid, а також про isClosed інстансів • Плюси data binding іноді можуть стати і недоліками. • Дивна підтримка Android Studio.
  20. • Не використовуйте фрагменти Поради • Жодного контексту у ViewModel

    • Замість колбеків можна використовувати ObservableField<EnumConstant>
  21. mainViewModel.activityState.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() { @Override public void onPropertyChanged(Observable observable, int i)

    { switch (mainViewModel.activityState.get()) { case FINISH: break; case START_FOR_NEXT_ACTIVITY: break; case SHOW_DIALOG: break; }}}); • Замість колбеків можна використовувати ObservableField<EnumConstant>
  22. https://github.com/patloew/countries An example Android app using Retrofit, Realm, Parceler, Dagger

    and the MVVM pattern with the data binding lib. Patrick Löwenstein
  23. https://mobiusconf.com/talks/a-modern-approach-to-the-architecture-of-android-app-rxja va-kotlin-mvvm/ Денис Неклюдов, 90seconds.tv, Android GDE Степан Гончаров, 90Seconds.tv

    Рассказ о том, как хорошо можно жить, когда у тебя DataBindings, RxJava, Kotlin, кэш на Firebase, DI на Dagger 2.