If there’s one thing that had a drastic change in the way I develop apps, it’s learning about Android architecture. Sure, I learned a few libraries in my journey, RxJava, Firebase, Retrofit, and they all helped, but nothing completely changed the way I write code than, well, changing the fundamentals of how you write.
Changing the architecture of my code-writing resulted in much cleaner, concise code. It was more modular and thus easier to maintain, easier for anyone else to make out, and just overall felt better.
I’ll be talking about my favourite one to use, the Model, View, View-Model architecture otherwise known as MVVM. This has a number of advantages over other architectures but I’ll get to that another week.
What is MVVM?
To understand what MVVM is, let’s go through the 3 parts that make it:
Model: This is the actual representation of the data, like a contract or a POJO class. You can also define methods where data is fetched, but none of them are called within the model.
View: The front end, like your activities and such.
View Model: This is the central location or bridge you might say, where all the work is done. The View Model gets an instance from the model, calls the data fetching methods defined in the model and presents the instance of the model with fresh data to the View which presents the data.
None of the data is changed in the View while the app is in operation. Any events and calls are sent to the View Model which updates the data and sends it back to the View to present. When you use observables (RxJava) in the View to receive callbacks from the View Model, this becomes a form of data binding.
A Simple Implementation of MVVM
I’m not gonna use any fancy schmuck here like RxJava. Tonight, we’re keeping simple minded and sticking to bare-bones Android with the new Breaded Shopping List in all its glory. For context sake, the only thing in the activity_main.xml layout will be a single ListView.
I’m gonna start by defining my Model, View, and View Model packages.
ShoppingList.kt (Model)
class ShoppingList(var items: ArrayList<String>) { constructor(): this(ArrayList()) fun getSampleItems() { items.addAll(arrayOf( "Bread", "Cheese", "Milk", "Cake")) } }
The model class is as simple as the POJO and its methods to fetch data. We’re using sample data here, but in a real application, this would be your database or network calls.
ShopViewModel.kt (View Model)
class ShopViewModel: ViewModel() { val shoppingList = ShoppingList() fun fetchSampleList(): ShoppingList { shoppingList.getSampleItems() return shoppingList } }
Extending the ViewModel class here is important because it let’s the data persist through lifecycle changes (like screen rotations). As you can see here, we have an instance of our model (ShoppingList) and the only method here calls the method within the model to fetch the data.
MainActivity.kt (View)
class MainActivity : AppCompatActivity() { lateinit var: viewModel: ShopViewModel val listItems = ArrayList<String>() lateinit var adapter: ArrayAdapter<String> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) viewModel = ViewModelProvider.AndroidViewModelFactory. getInstance(application).create(ShopViewModel::class.java) adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, listItems) listView.adapter = adapter updateList() } fun updateList() { listItems.clear() listItems.addAll(viewModel.fetchSampleList().items) adapter.notifyDataSetChanged() } }
All we’re doing here is setting up the list view and its adapter, then receiving the data from the View Model and updating the list. We also use the factory method to create the View Model so that lifecycle changes don’t affect it.
And the result is:
The list with all its items fetched from the view model.
Now you might be thinking, bit useless innit? We made 3 whole classes just to move data around like crazy and to that, you may be right… if this was all the app was.
Now imagine if your app was a full scale application that did this a million ways for different needs. Already, this style of coding makes the app cleaner, more concise, easier to read and maintain thanks to how modular it is.
Spicing it up with RxJava
But where it really shines is when you incorporate Data Binding with this architecture. I’ll be doing it with RxJava which is what you’ll see most people use, simply because of how good it is. If you haven’t yet learned RxJava and want to get into it, this should help you. Everything from here on out will assume you have a basic understanding of it.
class ShopViewModel(val shopRepository: ShopRepository) { fun fetchSampleList(): Observable<ShoppingList> { return shopRepository.getShoppingList() .map { ShoppingList(it) } } class ShopRepository { fun getShoppingList(): Observable<List<String>> = ShopApi.getShoppingList() } interface ShopApi { @GET("shoppinglist") fun getShoppingList(): Observable<List<String>> } }
The View Model changes drastically. We now have a repository and an API class and interface where the API acts as a data source and the repository handles retrieving data from data sources (in this case, it’s just the ShopApi, but a larger app may also have the cache, db, etc). We also return the observable to let the data flow asynchronously when it’s observed.
You might have noticed, we don’t need to extend the ViewModel class anymore, simply because RxJava powers through lifecycles for us.
class MainActivity : AppCompatActivity() { var viewModel = ShopViewModel(ShopViewModel.ShopRepository()) val compositeDisposable = CompositeDisposable() val listItems = ArrayList<String>() lateinit var adapter: ArrayAdapter<String> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, listItems) listView.adapter = adapter } private fun bindDataToList(shoppingList: ShoppingList) { listItems.clear() listItems.addAll(shoppingList.items) adapter.notifyDataSetChanged() } private fun bind() { compositeDisposable.add(viewModel.fetchSampleList() .subscribe(this::bindDataToList)) } private fun unbind() { compositeDisposable.clear() } override fun onResume() { super.onResume() bind() } override fun onPause() { unbind() super.onPause() } }
The View changes to accommodate these changes. All that’s happening here is we bind and unbind the observer on pause and resume. Now whenever fresh data is retrieved from the API, the list will update automatically.