model-view-viewmodel and rxjava

38
1 MVVM and RxJava – the perfect mix Florina Muntenescu GDG Android Berlin

Upload: florina-muntenescu

Post on 16-Apr-2017

352 views

Category:

Software


0 download

TRANSCRIPT

PowerPoint Presentation

MVVM and RxJava the perfect mixFlorina Muntenescu

GDG Android Berlin

#

1

What and who upday is

#upday is an aggregated news content platform that offers both machine and human recommended content. Our app is preinstalled on the Samsung Galaxy S7 and S7 edge for customers in France, Germany, Poland and the UK. We are integrated on the -1 page of the home screen one right swipe from the main home screen, opens our news service.The pre-installation brings with it millions of users but also a big responsibility: of building a stable, robust and easy to extend app that, on top of this, is also catchy and easy to use.

Thats a challenge!

I want to talk today about the turning point times in the development of the app. The ones where we were making the decisions that we knew will make us or break us.

2

When did we decide to go the RxJava & MVVM way?

#

3

The old appWhen did we decide to go the RxJava & MVVM way?

#3 months before delivering the final build to Samsung for the preinstallation, our situation was the following: we were 8 devs, most of us quite new to the project, needing to work on a code base that was started by another team.Developers always complain about the guys that developed the code before them. We complained also.

We had a sort of an MVC code, with the main action happening in the StreamFragment a monster class and the heart of the app! This fragment pretty much contained 3 different screens and user flows, put together in one. The fragment was actually the one that was deciding what and how to display, and was even telling the database what to save. 4

The old appWhen did we decide to go the RxJava & MVVM way?99 little bugs in the code.99 little bugs in the code.Take one down, patch it around.

127 little bugs in the code

#This is pretty much how messy our code was. You could see magic numbers, lots of checks for Null (since this is the best way to avoid NullPointerExceptions, right?), these kind of if statements with lots of conditions, comments that didnt actually say anything and not even the formatting wasnt correct.

Therefore, there shouldnt be any surprise that fixing one bug was creating 999 other bugs.

5

From the old to the newWhen did we decide to go the RxJava & MVVM way?

#At this point, the team was starting to use RxJava and MVVM here and there, in the new screens that needed to be created.

It was then when we were told that we need to do a complete redesign of the app. In 3 months.

To do new from old, with that StreamFragment we decided to take the leap and instead of going through the hard pain of working on top of the existing code, to practically do most of the things from scratch using RxJava and MVVM as main concepts in our app. Of course, some logic was moved, but most of it was just re-written.6

Crash course in RxJava

#ReactiveXis an API that focuses on asynchronous composition and manipulation of streams of data.RxJavais the open-source implementation of ReactiveX in Java. The two main classes are Observable and Subscriber. In RxJava, an Observable is a class that emits a stream of data, and a Subscriber is a class that acts upon the emitted items.7

Sum it up!Crash course in RxJava

A10A22

C13

combineLatest( (A1,A2) => A1+A2)0011

#Lets try to do an addition. Lets say that we want to add whatever is in A1 with whatever it is in A2.Thats pretty easy, right? In our sum field we just say that we want there =sum(A1, A2)

So, this means that we have a stream of events for A1 cell, a stream of events for A2 cell and our cell that contains the sum.

Since at the beginning we dont have anything in the cells, we consider that the value is 0.Whenever we have a new number in A1, our sum gets updated automatically.

In terms of RxJava A1 and A2 are called observables. And what we need to do is to combine the latest emission the 2 observables, creating a new one C1. The combineLatest method is actually called an operator. The class that acts upon the emitted items, is called the subscriber. In this example, the subscriber will be the one that will connect the emissions of every element of C1 and connect it it to the text view on the screen.

8

What is MVVM and how do you apply it?

#Why did we decide to use MVVM? Let me tell you what MVVM is and youll understand.MVVM is a variation of Martin Fowlers MVP.

9

Model-View-PresenterWhat is MVVM and how do you apply it?ModelViewPresenterIViewIPresenter1 1

#The MVP pattern has 3 components: A view, that informs the presenter about the user actionsThe presenter that tells the view what to display, meaning that we have a 2 way communication between the View and the Presenter. There is one-to-one relationship between View and Presenter means one Presenter is mapped to only one View.The presenter works with the model to get and save data. View has a reference to Presenter but View does not have a reference to the Model.10

Model-View-ViewModelWhat is MVVM and how do you apply it?DataModelViewViewModel1 *

#Like MVP, MVVM abstracts a views state and behavior.But, if the Presentation Model abstracts a view independent from a specific user-interface platform, the MVVM pattern was created by Microsoft to simplify the event driven programming of user interfaces.The ViewModel exposes streams of events that the views can bind to. Also, the views are notifying the ViewModel about different actions.Therefore, the MVVM pattern supports two-way data binding between the View and ViewModel and there is many-to-one relationship between View and ViewModel. View has a reference to ViewModel but ViewModel has no information about the View.What we are doing is more DataModel View- ViewModel, because the DataModel is the one that abstracts the data source. The event driven part is done using RxJavas Observables.

11

What is MVVM and how do you apply it?

#Things are easier to understand with examples. Lets take a simple Hello World and do it with MVVM. So, our app has a simple activity that contains a text view. The text view needs to display a text that comes from a model.

Lets take each component in a row and see what their role is and how we can implement our pattern.

12

Model-View-ViewModelWhat is MVVM and how do you apply it?DataModelViewViewModel1 *

#DataModel is the one that provides the greeting. Exposes the data that is easily consumable through event streams (in our case RxJavas Observables). Composes data from multiple sources (BE requests, database) and exposes them to whoever needs it. Its easy to unit test.

ViewModel in our case, it just exposes the greeting received from the data model. Is a model for the view of the app, an abstraction of the view. It exposes data relevant to the view and behaviors for the view. The VM is completely separated from the UI therefore straightforward to unit test.

ViewIs the actual user interface in the app the activity that contains the text view that we need to set. Views role is just to react on the events from the ViewModel. The View binds and un-binds from the event sources on onResume and onPause.In the bind method, were just saying that every time the view model emits a new greeting, it should be set as a text for our text view. Like this, we bind the data emitted by the view model with the text view.

MVVM combines the advantages of separation of concerns, provided by MVP, while leveraging the advantages of data bindings. Therefore, the result is a pattern where the model drives as much as the operations as possible, minimizing the logic in the view.

Since the view is just a consumer of the model, then its easy to just replace different UI elements, without needing to change any other layer. Because the ViewModel shouldnt change when changing the view, the regression testing comes for free, which helps reduce the cost of maintaining the app over time.

Lets see more about testing!

13

TestingWhat is MVVM and how do you apply it?

#So, how do we test our Hello World?

With Mockito we just mock the data model and we pass the mocked object to the constructor of the view model.

To check if indeed the view model emits the correct string, were just saying that when getGreeting is called, an observable containing the string that we defined needs to be returned. We subscribe then to the greeting and we expect that once subscribed, this will emit also the greeting that we previously defined.

So, since all of the logic is hold in the ViewModel, and we can mock the data model, then our class is easy to be tested.

With MVVM you have to look at things differently - Just consider the views and the unit tests as 2 different types of ViewModel consumers. If you want to do TDD then this is the right place.

The testability of ViewModel classes help designing interfaces. When you need to decide whether something needs to be in the View or in the ViewModel, imagine that you want to write a unit test to consume the VM. If you can write unit tests for the ViewModel without creating any UI objects, then you have your answer.14

Model-View-ViewModelWhat is MVVM and how do you apply it?DataModel

1 *ViewViewModel1 *ViewViewModel1 *ViewViewModel1 *ViewViewModel

#So, until now you might say that this doesnt sound so different from MVP but this small difference the many-to-one relationship, that fact that the ViewModel exposes streams of data, not knowing (and not caring) who consumes it, makes a big difference. In the end, the consumer should know about the producer, but not the other way around.

If the MVVM pattern can seem an overhead in small apps, its power is visible in bigger ones. Lets consider that you have complex views, with components that depend on one another and need to react on different changes. The separation of concerns and the single responsibility principles tell you that you need to split them, create ViewModels for all of them. Then just allow the view models to expose the right events, for other view models to consume. Not having the ViewModel dependent on a view, like the Presenter is, also allows them to be easily injected and makes the ViewModel independent from the state of the view.

Having the view model exposing streams of data leads also to a higher degree of testablity and a better control of working threads.

15

What is MVVM and how do you apply it?

1 *FunctionViewFunctionViewModel1 *SpreadsheetViewSpreadsheetViewModel

Observable getFunctionText()

#SpreadsheetViewModel

Because the function edit text is a complex one, with several functionalities, then it requires its own ViewModel - > FunctionViewModel

Whenever we type something in the edit text, the selected cell in the spreadsheet needs to be updated. Therefore, the FunctionViewModel can expose a method like Observable functionText().

The SpreadsheetViewModel can use this data and put it in the cell.So, the SVM and the FunctionView are consumers of the functionText()

16

What is MVVM and how do you apply it?

1 1FunctionViewimplements IFunctionViewFunctionPresenter1 1SpreadsheetViewSpreadsheetPresenter

implements IFunctionViewvoid textUpdated(String)

#How would this be implemented with MVP?

You would have a SpreadsheetPresenter and a FunctionPresenter, that uses a FunctionView, that implements Iview. Since the SpreadsheetPresenter needs to be notified of changes in the functionText, it would need to implement Iview and the FunctionPresenter would need to support a list of Iviews.So, the SpreadsheetPresenter is a presenter and a view in the same time.

17

#Summary:

Separation of concernsTestability Binding view models data emissions to components of the view

18

We made mistakes but we recovered!

#Everything sounds good in theory and for small apps but do this actually works in the real world? I want to tell you about some mistakes done and Im going to try to simplify them using our Hello world example.

19

We made mistakes but we recovered!

#Starting from our Hello World!, our product owner, decided that he wants to display also the city. For our UI design patterns, this means nothing, since the greeting would come from the DataModel. But, then our PO decides that if the greeting contains the word Berlin then he wants an image to be displayed. And this change needed to be done for tonights release.

20

ViewWe made mistakes but we recovered!

#What we did first was to say, in the view, that if the greeting contains the word Berlin then just set an image.

The app was doing what it needed and got released, our PO was happy. But we werent.21

ViewWe made mistakes but we recovered!

#What we did first was to say, in the view, that if the greeting contains the word Berlin then just set an image.

The app was doing what it needed and got released, our PO was happy. But we werent.22

ViewModelWe made mistakes but we recovered!

#We dont like UI tests, but we love unit tests.The view should not do logic decisions, the view model should. So we created a getImage stream, that just emits something when the greeting contains what it should.

The tests are simple and they allow us to test both the positive and the negative case.

Similar to the tests for the greeting, we mocked the data returned by the data model in the first test case, but returning a string that contains the word Berlin. Therefore, our expectation was that, when subscribed to the image emissions, then we indeed get the correct drawable.

In our test case, we create a greeting that does not contain the word Berlin, and, since we wanted this not to emit anything, we just assert that our subscriber has not indeed emitted any values. 23

Testing the ViewModel positive caseWe made mistakes but we recovered!

#

24

Testing the ViewModel negative caseWe made mistakes but we recovered!

#

25

ViewWe made mistakes but we recovered!

#In our View, things are equally simple. On bind, we just subscribe on the image events and we just set the image, when the event arrives.

Now, imagine this mistake of putting too much logic in the view in a bigger, complex app. It wasnt so pretty.

But then we took a step back, we realized that . this is not how things should work, that good programming requires principles like loose coupling, separation of concerns and single responsibility. Things that we knew but which could have been better applied.

So we moved the logic out from the view and in the view model. There, we refined the logic even more, keeping classes with only one (or at least few) responsibilities but making everything unit testable.

26

We made mistakes but we recovered!What about Views that dont have have lifecycle events?

#I was saying earlier that we bind and unbind from the observables on onResume and onPause. But what do we do when we dont have an Activity or a Fragment acting as a View in our MVVM pattern?What if we actually have a View? So no onResume and onPause methods.

27

We made mistakes but we recovered!Views and subscriptions

#Well usually, we have something like this

But the problem here is that the subscription is preserved unncesarelly and can lead to memory leaks.

28

We made mistakes but we recovered!Views and subscriptions

#Whats the solution? Call unbind on onDetachedFromWindow

29

#We learned 2 important things:

Keep all logic away from the ViewMake sure you unbind from all subscriptions30

RxJava the good and the bad

#I keep on mentioning here RxJava but, if MVVM pattern was created by Microsoft to implement user interfaces using event driven programming then, is RxJava really the best option? Lets see what are the advantages of Rx compared to other possibilities but also, what are the disadvantages.

31

RxJava the good and the badRxJava vs Threads/AsyncTask/Event bus frameworks

#Think how you would do this with Threads/AsyncTasks. RxJava allows better handling of Activity/Fragment lifecycle, caching on rotation, composing multiple streams, error handling and testability

How would you do this with event bus frameworks? Compared to Event Bus frameworks RxJava offers readability, ease of debugging, less boilerplate, capability of filtering the results and manipulate the data.

32

Stream composability FTW!RxJava the good and the bad

#Observables help provide simple, yet asynchronous APIs but they really shine with their composability aspects.

Remember our excel example from the RxJava crash course? Lets see how this would look when actually implemented. We would have a method - get sum, that would receive the A1 and A2 observables as parameters and it would combine the latest emissions of the 2, using a function. In our case, that function is a sum. Combining 2 threads or events from 2 different sources using event bus framework, would require a lot of boiler plate. But like this, the code is simple and easy to read.

RxJavas operators are elements that make iterative operations look nice and easy to understand, when done in a functional way. And, most of all, in general, RxJava code is testable!

All of this sounds amazing! RxJava is magic! But are there any dark sides of this magic?

33

Split a string using a regular expression and append a new line to each item in the resulting listRxJava the good and the badRxJava the good and the bad[RxJava\n, the\n, good\n, and\n, the\n, bad\n]

#Using operators, transformers and other functionalities offered by Rx require quite a high learning curve.But, once developers understand how these work, they tend to use it for everything. I want to show you an example from our own production code the Rx way. Then I will show you how it looks without Rx and well try to find out whether Rx was really the right solution. The task is simple: split a string using a regular expression and then append a new line to each item in the resulting list. 34

RxJava the good and the bad

map(line -> line.trim() + NEW_LINE)

toList()RxJavathegoodRxJava\n the\ngood\n["RxJava\n, the\n, good\n ...]

#The developer that implemented this is really familiar with Rx so lets see what he did here:It converts the list resulted from splitting of the text to an Observable. So this means that every item of the split will be emissions of our observable. Every string emitted will be trimmed and then a new line will be added. Since we need a list to be retuned, the operator ToList is used to gather all the emissions in a single list. Then, since we dont need an observable of list but the actual list, this means that we dont need this entire operation to happen asynchronous so we are using toBlocking to make it synchronous. single is the operator that returns us the list.

Given that it took me 5 minutes to explain 6 lines of code, its easy to see that, especially for beginners, RxJava code can seem hard to read.

35

Split a string using a regular expression and append a new line to each item in the resulting listRxJava the good and the bad

#Lets see how we can do this iteratively: we just get an array of the text spit, we iterate through it and create a new list containing the words trimmed and with a new line added. Well.. That sounded simple.

The only advantage that Rx has in this case, its just that its prettier. Both methods are actually synchronous and even more, Rx way is slower in this case. In general for simple operations like this, the declerative way is slower.

When not implemented properly, RxJava brings with it sometimes racing issue or in general, possible threading problems.But dont let yourself be scared by these possible issues. A lot of them have already been addressed and solved and most of them, just require reading and understanding of the API.

36

#Title: RxJava the good and the bad

Doing async operations is easy in Rx After youre over the initial steep learning curve

37

RxJava and MVVM the perfect mixSimple MVVM example: https://github.com/florina-muntenescu/DroidconMVVMMVP vs MVVM example: https://github.com/florina-muntenescu/MVPvsMVVM

upday tech blog: https://upday.github.io/

Exclusive to [email protected]

#

#The leap took when we did the redesign allowed us to establish a very good backbone of the app and provided us with a great overall strategy in terms of architecting our app.We learned a lot in these months, sometimes the hard way, but now we are in a state where the app is quite separate, responsibilities of the classes are pretty well determined and adding multiple features in the same time or fixing issues can be done with minimum interference. No merge conflicts makes developers happy.

38