Search code examples
androidmvvmandroid-databinding

Android MVVM with Programmatic UI Customization


I'm trying to nail down proper MVVM layering in an app with the Android data binding lib. Really simple layouts, like in most examples, abstract nicely with XML layouts binding to methods in my View Model for event handling and model updates via observables. However, more often than not, there's UI customization that needs to be done programmatically (findViewById()...) and/or through things like injecting styling attributes into string resources.

Should I just treat the Activity/Fragment as also part of the View and do whatever can't be handled between the VM and layout via databinding, or is it better to just handle this with an interface from the VM to the Activity/Fragment (while trying to keep the VM a POJO)?

-- EDIT: Example1 --

Rendering a TextView with multiple colors in the same string: How I originally had this implemented was wrapping CDATA and font tags in the string resource and rendering with findViewById().setText(Html.fromHtml(getString(..))). I've modified my layout to instead bind in my VM as android:text="@{viewModel.text1}", which calls an interface method to my fragment that returns Html.fromHtml(text), and my VM returns a Spanned to the layout. Thinking strict MVVM, I probably wouldn't define the VM this way so it feels like a little hacky.


Solution

  • As a rule of thumb I would not put anything into the ViewModel that needs an Android Context. On the other hand all logic should be located in the ViewModel so that you can Unit Test it.

    For example if you want a TextView to display either String A or B depending on a decision that is made in the ViewModel you can create an interface that is an abstraction of Android Resources for you and pass this interface from the View into the ViewModel.

    public MyViewModel {
    
     private MyStrings strings;
    
     public MyViewModel(MyStrings strings) { 
       this.strings = strings;
     }
    
     public String getMyString() {
       return becauseOfReasons() ? strings.getA() : strings.getB();
     }
    
      public interface MyStrings {
       String getA();
       String getB();
      } 
    }
    

    For your example you wanted to use Html.fromHtml programmatically. What's really great about the Android Databinding Library is that you can use access Html.fromHtml() from the xml. So in your case the xml could look like this:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
    
        <import type="android.text.Html"/>
    
        <variable
            name="viewModel"
            type="some.package.MyViewModel"/>
    
      </data>
    
      <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{Html.fromHtml(viewModel.myString)}"/>
    </layout>
    

    However this is a pretty radical approach. I would recommend to always consider if your ViewModel remains testible. For example you can still create JUnit Tests when you access resource ids from R class in the ViewModel. But on the other hand you should not use Html.fromHtml in the ViewModel because, you will get a not mocked error.