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

Getting started with MVVM

GDG Cherkasy
February 24, 2017

Getting started with MVVM

Назар Илмедов (Nazar Ilmedov) - Android developer
Спростити собі життя під час розробки Android додатку можна! Зокрема, за допомогою розбиття проекту на прошарки та розподілення відповідальності між ними. Одним із таких рішень є архітектурний паттерн MVVM, про який я і хочу розповісти. Я розкажу про його застосування в Android реаліях та побудову додатку, який можна з легкістю тестувати без думок про самогубство.

GDG Cherkasy

February 24, 2017
Tweet

More Decks by GDG Cherkasy

Other Decks in Programming

Transcript

  1. • Separate view and logic • Avoid the problem of

    “God Object” that knows too much (Activity/Fragment) • Easy to change • Easy to test
  2. • Model - Provider of data we want to display

    in the View • View - Responsible for defining the layout, appearance, displaying data and delegating user actions to the Presenter. Implements by Activity/Fragment/ViewGroup. • Presenter - Acts as an intermediary between the View and Model, dealing with any view logic. It retrieves data from the Model and returns it formatted to the View. Also decides what happens when you interact with the View.
  3. Conclusion + Logic and view separation + Easy to read

    + Easy to change + Easy to test by unit testing + A lot of code related to View management
  4. • Model - Provider of data we want to display

    in the View • View - Responsible for defining the layout, appearance, displaying data and delegating user actions to the ViewModel. Implements by Activity/Fragment/ViewGroup. • ViewModel - Acts as an intermediary between the View and Model, dealing with any view logic. Represents View in subject area, connected with View by Data Binding mechanism.
  5. Data Binding - the process that establishes a connection between

    the UI and data elements. When the data changes its value, the UI elements, that are bound to the data reflect changes automatically and vice versa. What is Data Binding?
  6. https://developer.android.com/topic/libraries/data-binding/index.html Android Data Binding Library android { ... dataBinding {

    enabled = true } } To configure your app to use data binding, add the dataBinding element to your build.gradle file in the app module. Use the following code snippet to configure data binding:
  7. Data binding layout files are slightly different and start with

    a root tag of layout followed by a data element and a view root element. This view element is what your root would be in a non-binding layout file. A sample file looks like this:
  8. Observable Objects A class implementing the Observable interface will allow

    the binding to attach a single listener to a bound object to listen for changes of all properties on that object. This is done by assigning a Bindable annotation to the getter and notifying in the setter.
  9. BindingAdapter BindingAdapter is applied to methods that are used to

    manipulate how values with expressions are set to views. @BindingAdapter ("bind:imageUrl") public static void loadImage(ImageView imageView, String imageUrl) { Glide.with(imageView.getContext()) .load(imageUrl) .centerCrop() .into(imageView); } <ImageView android:layout_width="match_parent" android:layout_height="match_parent" app:imageUrl="@{user.avatarUrl}"/> in XML:
  10. public class SignInViewModel extends BaseObservable { private String email; private

    String password; private boolean showProgressBar; ... public void onClickSignInButton() { ... } @Bindable public String getEmail() { return email; } public void setEmail(String email) { this.email = email; notifyPropertyChanged(BR. email); } @Bindable public String getPassword() { return password; } public void setPassword(String password) { this.password = password; notifyPropertyChanged(BR. password); } @Bindable public boolean isShowProgressBar() { return showProgressBar; } public void setShowProgressBar( boolean showProgressBar) { this.showProgressBar = showProgressBar; notifyPropertyChanged(BR. showProgressBar); } }
  11. <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="android.view.View"/> <variable name="viewModel" type="nilmedov.appmvvm.viewmodels.SignInViewModel"/> </data> ...

    <EditText ... android:text="@={viewModel.email}"/> <EditText ... android:text="@={viewModel.password}"/> <Button ... android:onClick="@{() -> viewModel.onClickSignInButton()}"/> <ProgressBar ... android:visibility="@{viewModel.showProgressBar ? View.VISIBLE : View.GONE}"/>
  12. public class SignInActivity extends AppCompatActivity { private ActivitySignInBinding binding; @Override

    protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil. setContentView(this, R.layout.activity_sign_in); binding.setViewModel( new SignInViewModel()); } ... View
  13. public class ItemRepositoryViewModel extends BaseObservable { private Repository repository; public

    ItemRepositoryViewModel(Repository repository) { setRepository(repository); } @Bindable public Repository getRepository() { return repository; } public void setRepository(Repository repository) { this.repository = repository; notifyPropertyChanged(BR. repository); } } ViewModel for RecyclerView
  14. In XML <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="nilmedov.appmvvm.viewmodels.ItemRepositoryViewModel" /> </data>

    <RelativeLayout android:layout_width="match_parent" android:layout_height="150dp"> <TextView ... android:text="@{String.valueOf(viewModel.repository.stars)}" /> <TextView ... android:text="@{viewModel.repository.name}"/> <TextView ... android:text="@{viewModel.repository.description}"/> </RelativeLayout> </layout>
  15. private class RepositoryHolder extends RecyclerView.ViewHolder { private ItemRepositoryBinding binding; public

    RepositoryHolder(ItemRepositoryBinding binding) { super(binding.getRoot()); this.binding = binding; } void onBind(Repository repository) { if (binding.getViewModel() == null) { binding.setViewModel( new ItemRepositoryViewModel(repository)); } else { binding.getViewModel().setRepository(repository); } } } In Adapter
  16. Model public interface UserModel { Observable<NetworkResponse<User>> signIn(String email, String password);

    @Nullable User getUserFromSharedPrefs(); @Nullable String getAuthTokenFromSharedPrefs(); void saveUserInSharedPrefs( @NonNull User user); void saveAuthTokenInSharedPrefs( @NonNull String token); void removeUserFromSharedPrefs(); void removeAuthTokenFromSharedPrefs(); }
  17. 1) How to show toast, dialog, start new screen, or

    animation from ViewModel? • Have a reference of View interface inside ViewModel and invoke its methods 2) What if data returns from the Model when View has been destroyed and haven’t recreated yet? ViewModel should know when view creates and destroys. • Post events by event bus (be careful, you shouldn’t have few subscriptions on the same event at the same time) • Write own mechanism to hold received data and set it when View has recreated • Use RxJava and cache event, that observable emits when data is ready. Then just unsubscribe on Observable when View destroys and subscribe again when View has recreated.
  18. How to make ViewModel configChange persistent? • Put it into

    the Loader! • Use it in retained instance of Fragment • Use Dagger 2 Scoping • Write a holder for ViewModel • Come up with own solution :)
  19. Tests? Tests! @Test public void testSignIn() { signInViewModel.onViewAttached( signInView); signInViewModel.setEmail("right");

    signInViewModel.setPassword( "credentials"); assertEquals(signInViewModel.getEmail(), "right"); assertEquals(signInViewModel.getPassword(), "credentials"); assertFalse(signInViewModel.isShowProgressBar()); signInViewModel.onClickSignInButton(); verify(signInViewModel).setShowProgressBar( true); assertFalse(signInViewModel.isShowProgressBar()); verify(signInViewModel.userModel).saveAuthTokenInSharedPrefs(Mockito. any()); verify(signInViewModel.userModel).saveUserInSharedPrefs(Mockito. any()); verify(signInView).moveToMainView(); }
  20. • Don’t use Android classes as Context, View, Log, etc…

    in ViewModel. “If it contains the line import android.content.Context;, you’re doing it wrong. Don’t do it. Kittens die.” • But if you want so badly, use Robolectric • Use dependency injection (Dagger 2) for ViewModels • If you use RxJava, all work should be doing in main thread. Change schedulers via dependency injection or write TestRule for RxJava Some tips for building testable application
  21. Conclusion + MVVM separates view and logic + You don’t

    have to interact with view inside ViewModel (or do it less than in Presenter) + Even better to test than Presenter +- Data binding magic + You have to deal with lifecycle anyway + Sometimes need to interact with View not via data binding
  22. QA