Implementing LiveData – coming from C#

I've been developing in C# since approx. 2008 – I made my first app, a simple SVN client, in that very year (the app was for, drumroll please, easier management of Garry's Mod addons, before Steam even had the idea for their own interface. Most addons were stored on various free SVN servers, thus an updater/downloader came quite handy).

I've also been quite interested in Android since the announcement by Google. In 2006/2007 I was the kid in primary school who was running around with the printed out documentation, learning every bit of it.

But after the simplicity of WinForms and WPF, Android was completely alien for me. Sure I did some development, but it didn't feel right.

Come today, and I'm facing an issue that most likely roots from my experience with C#. LiveData<T> is pretty awesome – I started using it recently and with AndroidX ViewModels and lifecycle-aware stuff, it is almost as easy to use as say, MvvmCross for Xamarin (which is in itself an absolutely must-have framework for any and all Xamarin developers! One of my main reasons behind not using Dagger (2) was the simplicity I've learned from MvvmCross – and it is the reason why I mostly refuse to use the Navigation component, since its logic is so backwards.

In retrospective, MvvmCross handles the navigation completely different: instead of going from View to View, with a data- and logic-carrying ViewModel attached to the Views, it instead navigates from ViewModel to ViewModel (allowing easier communication between elements, i.e. you can easily pass parameter objects, references, without worrying about type safety and parceling and whatnot), and the View is annotated to belong to said ViewModel (this way it's freaking easy to navigate in apps where the common logic, models, viewmodels, etc. are indeed common, but views are natively defined in their platform-specific subprojects). Each platform/target can have a single View attached to a single ViewModel.

So with the release of LiveData<T> I was quite happy initially – finally a worthy competitor of C#'s observable properties! Combine it with Kotlin and we got it going on, baby… Or so I thought.

I've tried making a simple sample app. It's really really dumb:

An API service, completely blackboxed, using coroutines and LiveData, returning a list of objects A repository, requesting updates from the above client, storing it in Db, and retrieving Db info with LiveData It can also retrieve a single object for a Detail screen Both the single object and the list of objects is stored as LiveData<T> and LiveData<List<T>> objects The single property is populated manually The list property is assigned from database.dao().getAll() The single property is populated by a collation request, where I retrieve the root object, and all of its child objects manually (don't look at me that weird, I still dislike the joint DAOs Room forces people to use for relationships) Two ViewModels: ListViewModel, that provides access to the repository's list property DetailViewModel, that provides access to the repository's single property (and calls the appropriate function in the repo to populate it)

The list works awesomely. I can see real-time updates to the database, it's quick, and just works. However I can't get the detail screen to work properly.

This is my repository:

class ObjectRepository { val db: AppDatabase by inject() // Yes, I'm using Koin val client: ApiClient by inject() //region Coroutine helpers private var parentJob = Job() private val coroutineContext: CoroutineContext get() = parentJob + Dispatchers.Main private val scope = CoroutineScope(coroutineContext) //endregion val object: LiveData<MyObject> = MutableLiveData() val objects: LiveData<List<MyObject>> = db.objectDao().getAll() fun update() { // Download data here, convert DTO to DAO, and insert into db } fun getObject(id: Int) { scope.launch(Dispatchers.IO) { val data = db.objectDao().get(id) // get returns the item blocking, getAll returns LiveData-wrapped // Get extra properties of data (object as MutableLiveData<MyObject>).postValue(data) } } }

And my ViewModels:

// BaseViewModel is nothing fancy, it has a bunch of small helpers mostly, extends ViewModel and implements KoinComponent class ListViewModel: BaseViewModel() { private val repo: ObjectRepository by inject() val objects: LiveData<List<MyObject>> = repo.objects fun update() { repo.update() } } class DetailViewModel: BaseViewModel() { private val repo: ObjectRepository by inject() val object: LiveData<MyObject> = repo.object val text: String = "" fun getObject(id: Int) { repo.getObject(id) } }

The one thing I could not overcome is using properties of a LiveData-wrapped object. The class MyObject has three properties: name, content, image. In C#, this works quite well, since the type of the field is still MyObject and not a wrapper, so in any layout binding I can just refer to with ease.

However this does not seem to be possible with Android DataBinding. Whenever I try to do the following in my DetailView, I get a compile error:

<layout …xml ns definitions here…> <data> <variable name="viewModel" type="" /> </data> <root whatever> <TextView … android:text="@{}" /> // Does not work, fails to compile <TextView … android:text="@{}" /> // Compile fails <TextView … android:text="@{viewModel.object}" /> // Compile fails <TextView … android:text="@{viewModel.text}" /> // Works fine </root> </layout>

So how could I access a LiveData-wrapped object's properties/fields in layout binding? Is there a simple solution for this, or should I consider either exposing all the fields as separate properties in my ViewModel?

submitted by /u/fonix232 [link] [comments]

Read more:

Share this:

Leave a Reply

Your email address will not be published. Required fields are marked *