VGTech is a blog where the developers and devops of Norways most visited website share code and tricks of the trade… Read more



Are you brilliant? We're hiring. Read more

Android Databinding: Goodbye Presenter, hello ViewModel!

Android

The Model-View-Presenter-pattern (MVP) has been the dominating trend lately when it comes the UI-layer architecture of Android applications. Frameworks like Ted Mosby, Nucleus and Mortar have all talked about Presenters to help you achieving a clean architecture of your app. They also (to a varying degree) help you with the infamous issues of device rotation and state persistence on the Android platform. This isn’t directly related to the concept of MVP, but the pattern helps you isolate the boiler plate code.

Data Binding, as announced on Google I/O 2015 and shipped with the Android M preview as a support library, changes everything. According to the Wikipedia article on MVP, the Presenter has the following tasks:

The presenter acts upon the model and the view. It retrieves data from repositories (the model), and formats it for display in the view.

The thing is that Data Binding framework will take over the main responsibilities of the Presenter (“acting upon the model and the view”), while the remainder is left to the enhanced Model – the ViewModel (“retreiving data from repositories and formatting”). The ViewModel is a standard Java class whose sole responsibility is to represent the data behind a single View. It can merge data from multiple sources (Models) and prepare that data for presentation. I did a short writeup on the ViewModel and how it differs from Data Model or Transport Model.

The architecture we end up with is MVVM – Model-View-ViewModel, and is a proven concept originally coined by Microsoft back in 2005 (don’t let that scare you ;-) ). Let me illustrate the change from MVP to MVVM for you, shamelessly copying Hanne Dorfmann’s illustration from the introduction of his Ted Mosby framework

mvp
mvvm

So all the binding and updating of data to the view is done through the Data Binding Framework. The ObservableField class allow the View to react on changes to the model, and the XML references to fields allow the framework to push changes back to the ViewModel when the user acts upon the View. You can also programatically subscribe to changes in fields so that for instance a TextView is disabled when a CheckBox is clicked. One great advantage of representing the visual state of the View in a standard Java class like this is clear: You can easily unit test the visual behaviour.

Note that in the MVP illustration above there is a method call to Presenter.loadUsers(). This is a Command. In MVVM these are methods defined in the ViewModel. From the Wikipedia article:

The view model is an abstraction of the view that exposes public properties and commands

So this may or may not be a change to what you’re used to. In the MVP pattern it’s likely that your models were “dumb” classes only holding data. Don’t be afraid of putting business logic in your Models or View Models as well. This is a core principle of Object Oriented Programming. So back to Presenter.loadUsers() – this will now be a method in the ViewModel and may be invoked programmatically by the code-behind* of your View or though a data bound command in the XML of your View. That is – if the promises of this issue in the android-developer-preview issue tracker hold up. If we don’t get data binding to commands, we have to resort to the old android:onClick-syntax, or manually adding listeners in the View code as before.

*) “Code-behind” is a term from Microsoft, often with negative associations to early ASP.NET or WinForms. I think it’s a describing term also on Android where the View is composed of two source elements: the View Layout (XML) and the Code-Behind (Java), represented by Fragments, Activities and classes extending View.java.

Dealing with system calls

There is one set of use cases which still have to be done in the code-behind of the View – functions which initate system calls, opens dialogs or basically any call which require reference the Context object of Android. Don’t put code like this in the ViewModel. If it contains the line import android.content.Context;, you’re doing it wrong. Don’t do it. Kittens die.

I haven’t quite made up my mind on the best approach to tackle this yet, but that’s because there are several good options. One way would be to keep elements of the presenter concept from Mosby by referencing an interface to the View in the ViewModel. This way you won’t reduce the testability. But instead of having a seperate Presenter class as in Mosby, I’d stick to the View as the concrete implementation of that interface just to keep it simple. Another approach could be to use an event bus like Square’s Otto to initiate commands like new ShowToastMessage("hello world"). This will yield a greater separation of the view and the viewmodel – but is that a good thing?

Don’t we need frameworks now?

So is the Data Binding framework taking over the job of framworks like Mosby or Mortar? Only partly. What I hope to see is these frameworks evolve or fork into MVVM-style frameworks so that we can leverage the best of Data Binding while keeping the dependencies to 3rd-party frameworks to a minimum, and keeping the frameworks small and simple. While the era of the Presenter might be over, the framworks do a just as important job with lifecycle management and view state (ViewModel) persistence. This has not changed (unfortunately*).

*) Wouldn’t it be cool if Google introduced an interface LifeCycleAffected which Fragment, Activity and View implemented? And that interface had methods named addOnPauseListener() and addOnResumeListener()? I leave it up to you to what that might have done with our code base, and the code base of MVP/MVVM frameworks.

update: I’ve recently learned about the AndroidViewModel framework, which actually might be a very good fit for MVVM and Android Data Binding. I’ve not had the time to try this out yet though.

Summary

When I first heard that Android M was all about improving the SDK and focusing on the developers, I was really excited. When I heard they were introducing Data Binding, I was stoked. I have worked for years with data binding on other platforms : WinForms, WPF, Silverlight and Windows Phone. I know it will help us write cleaner architecture and less boiler plate code. The framework works with us instead of against us, as I’ve felt it has been for a long time.

But it’s not a silver bullet – there are downsides. The fact that you define stuff in XML is an issue. XML is not compiled. XML cannot be unit tested. Hence you’ll often end up noticing errors run-time instead of compile time. Forgot to bind the field to the view? Tough luck. But the tooling can help a great deal here – that’s why I hope Google is focusing hard on making Android Studio support data binding to the max. Syntax and reference checking of the binding XML, auto complete and navigation support. Field renaming support which propagates to XML. From my testing of Android Studio 1.3 beta – I’m at least assured that the are thinking about it. Some things are supported. A lot isn’t, but we’re still in beta so here’s hoping.

Code example

So here is a quick sample I threw together where I try to illustrate the conseqeunces of going from MVP to an MVVM architecture. I used Mosby as a framework in the MVP version with Butterknife for view injection. In the MVVM example I’m using Android M databinding and drop dependencis to both Mosby and Butterknife. The result is that the Presenter can be dropped, the Fragment gets less code but the ViewModel takes over a lot of the code.

In this example I directly reference the View in order to produce toast messages. This may or may not be an approach I advocate later, but in theory we’re not doing anything wrong here. It’s testable if you mock the Fragment using Robolectric and Mockito, and doesn’t leak memory unless you start referencing ViewModels where you should’n’t.

The app is basically a login screen with som async data loading and inter-view dependencies just for illustration purposes.

illustration

If you prefer to read the code in Android Studio – here’s the github repo. Check out the MVP and MVVM tags respectively.

Now, prepare for wall of code:

MVP – View – XML

Show code
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin"
                tools:context=".MainActivityFragment">

    <TextView
        android:text="..."
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="true"
        android:id="@+id/loggedInUserCount"/>

    <TextView
        android:text="# logged in users:"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentEnd="false"
        android:layout_toLeftOf="@+id/loggedInUserCount"/>

    <RadioGroup
        android:layout_marginTop="40dp"
        android:id="@+id/existingOrNewUser"
        android:gravity="center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:orientation="horizontal">

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Returning user"
            android:id="@+id/returningUserRb"/>

        <RadioButton
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="New user"
            android:id="@+id/newUserRb"
            />

    </RadioGroup>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/username_block"
        android:layout_below="@+id/existingOrNewUser">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Username:"
            android:id="@+id/textView"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/username"
            android:minWidth="200dp"/>
    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentStart="false"
        android:id="@+id/password_block"
        android:layout_below="@+id/username_block">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Password:"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textPassword"
            android:ems="10"
            android:id="@+id/password"/>

    </LinearLayout>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/password_block"
        android:id="@+id/email_block">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium"
            android:text="Email:"
            android:minWidth="100dp"/>

        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:inputType="textEmailAddress"
            android:ems="10"
            android:id="@+id/email"/>
    </LinearLayout>

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Log in"
        android:id="@+id/loginOrCreateButton"
        android:layout_below="@+id/email_block"
        android:layout_centerHorizontal="true"/>
</RelativeLayout>
   

MVP – View – Java

Show code
package com.nilzor.presenterexample;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import com.hannesdorfmann.mosby.mvp.MvpFragment;
import com.hannesdorfmann.mosby.mvp.MvpView;
import butterknife.InjectView;
import butterknife.OnClick;

public class MainActivityFragment extends MvpFragment implements MvpView {
    @InjectView(R.id.username)
    TextView mUsername;

    @InjectView(R.id.password)
    TextView mPassword;

    @InjectView(R.id.newUserRb)
    RadioButton mNewUserRb;

    @InjectView(R.id.returningUserRb)
    RadioButton mReturningUserRb;

    @InjectView(R.id.loginOrCreateButton)
    Button mLoginOrCreateButton;

    @InjectView(R.id.email_block)
    ViewGroup mEmailBlock;

    @InjectView(R.id.loggedInUserCount)
    TextView mLoggedInUserCount;

    public MainActivityFragment() {
    }

    @Override
    public MainPresenter createPresenter() {
        return new MainPresenter();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        attachEventListeners();
    }

    private void attachEventListeners() {
        mNewUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                updateDependentViews();
            }
        });
        mReturningUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                updateDependentViews();
            }
        });
    }

    /** Prepares the initial state of the view upon startup */
    public void setInitialState() {
        mReturningUserRb.setChecked(true);
        updateDependentViews();
    }

    /** Shows/hides email field and sets correct text of login button depending on state of radio buttons */
    public void updateDependentViews() {
        if (mReturningUserRb.isChecked()) {
            mEmailBlock.setVisibility(View.GONE);
            mLoginOrCreateButton.setText(R.string.log_in);
        }
        else {
            mEmailBlock.setVisibility(View.VISIBLE);
            mLoginOrCreateButton.setText(R.string.create_user);
        }
    }

    public void setNumberOfLoggedIn(int numberOfLoggedIn) {
        mLoggedInUserCount.setText(""  + numberOfLoggedIn);
    }

    @OnClick(R.id.loginOrCreateButton)
    public void loginOrCreate() {
        if (mNewUserRb.isChecked()) {
            Toast.makeText(getActivity(), "Please enter a valid email address", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(getActivity(), "Invalid username or password", Toast.LENGTH_SHORT).show();
        }
    }
}

MVP – Presenter

Show code
package com.nilzor.presenterexample;

import android.os.Handler;
import android.os.Message;
import com.hannesdorfmann.mosby.mvp.MvpPresenter;

public class MainPresenter implements MvpPresenter {
    MainModel mModel;
    private MainActivityFragment mView;

    public MainPresenter() {
        mModel = new MainModel();
    }

    @Override
    public void attachView(MainActivityFragment view) {
        mView = view;
        view.setInitialState();
        updateViewFromModel();
        ensureModelDataIsLoaded();
    }

    @Override
    public void detachView(boolean retainInstance) {
        mView = null;
    }

    private void ensureModelDataIsLoaded() {
        if (!mModel.isLoaded()) {
            mModel.loadAsync(new Handler.Callback() {
                @Override
                public boolean handleMessage(Message msg) {
                    updateViewFromModel();
                    return true;
                }
            });
        }
    }

    /** Notifies the views of the current value of "numberOfUsersLoggedIn", if any */
    private void updateViewFromModel() {
        if (mView != null && mModel.isLoaded()) {
            mView.setNumberOfLoggedIn(mModel.numberOfUsersLoggedIn);
        }
    }
}

MVP – Model

Show code
package com.nilzor.presenterexample;

import android.os.AsyncTask;
import android.os.Handler;
import java.util.Random;

public class MainModel {
    public Integer numberOfUsersLoggedIn;
    private boolean mIsLoaded;
    public boolean isLoaded() {
        return mIsLoaded;
    }

    public void loadAsync(final Handler.Callback onDoneCallback) {
        new AsyncTask() {
            @Override
            protected Void doInBackground(Void... params) {
                // Simulating some asynchronous task fetching data from a remote server
                try {Thread.sleep(2000);} catch (Exception ex) {};
                numberOfUsersLoggedIn = new Random().nextInt(1000);
                mIsLoaded = true;
                return null;
            }

            @Override
            protected void onPostExecute(Void aVoid) {
                onDoneCallback.handleMessage(null);
            }
        }.execute((Void) null);
    }
}

MVVM – View – XML

Show code
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable name="data" type="com.nilzor.presenterexample.MainModel"/>
    </data>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin"
        android:paddingBottom="@dimen/activity_vertical_margin"
        tools:context=".MainActivityFragment">

        <TextView
            android:text="@{data.numberOfUsersLoggedIn}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:id="@+id/loggedInUserCount"/>

        <TextView
            android:text="# logged in users:"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="false"
            android:layout_toLeftOf="@+id/loggedInUserCount"/>

        <RadioGroup
            android:layout_marginTop="40dp"
            android:id="@+id/existingOrNewUser"
            android:gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:orientation="horizontal">

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Returning user"
                android:checked="@{data.isExistingUserChecked}"
                android:id="@+id/returningUserRb"/>

            <RadioButton
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="New user"
                android:id="@+id/newUserRb"
                />

        </RadioGroup>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/username_block"
            android:layout_below="@+id/existingOrNewUser">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Username:"
                android:id="@+id/textView"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/username"
                android:minWidth="200dp"/>
        </LinearLayout>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="false"
            android:id="@+id/password_block"
            android:layout_below="@+id/username_block">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Password:"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:inputType="textPassword"
                android:ems="10"
                android:id="@+id/password"/>

        </LinearLayout>

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/password_block"
            android:id="@+id/email_block"
            android:visibility="@{data.emailBlockVisibility}">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textAppearance="?android:attr/textAppearanceMedium"
                android:text="Email:"
                android:minWidth="100dp"/>

            <EditText
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:inputType="textEmailAddress"
                android:ems="10"
                android:id="@+id/email"/>
        </LinearLayout>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{data.loginOrCreateButtonText}"
            android:id="@+id/loginOrCreateButton"
            android:layout_below="@+id/email_block"
            android:layout_centerHorizontal="true"/>
    </RelativeLayout>
</layout>

MVVM – View – Java

Show code
package com.nilzor.presenterexample;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CompoundButton;
import android.widget.Toast;

import com.nilzor.presenterexample.databinding.FragmentMainBinding;

public class MainActivityFragment extends Fragment {
    private FragmentMainBinding mBinding;
    private MainModel mViewModel;

    public MainActivityFragment() {
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        mBinding = FragmentMainBinding.bind(view);
        mViewModel = new MainModel(this, getResources());
        mBinding.setData(mViewModel);
        attachButtonListener();
        return view;
    }

    private void attachButtonListener() {
        mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mViewModel.logInClicked();
            }
        });
    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        ensureModelDataIsLodaded();
    }

    private void ensureModelDataIsLodaded() {
        if (!mViewModel.isLoaded()) {
            mViewModel.loadAsync();
        }
    }

    public void showShortToast(String text) {
        Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
    }
}

MVVM – ViewModel

Show code
package com.nilzor.presenterexample;

import android.content.res.Resources;
import android.databinding.ObservableField;
import android.os.AsyncTask;
import android.view.View;

import java.util.Random;

public class MainModel {
    public ObservableField numberOfUsersLoggedIn = new ObservableField();
    public ObservableField isExistingUserChecked = new ObservableField();
    public ObservableField emailBlockVisibility = new ObservableField();
    public ObservableField loginOrCreateButtonText = new ObservableField();
    private boolean mIsLoaded;
    private MainActivityFragment mView;
    private Resources mResources;

    public MainModel(MainActivityFragment view, Resources resources) {
        mView = view;
        mResources = resources; // You might want to abstract this for testability
        setInitialState();
        updateDependentViews();
        hookUpDependencies();
    }
    public boolean isLoaded() {
        return mIsLoaded;
    }

    private void setInitialState() {
        numberOfUsersLoggedIn.set("...");
        isExistingUserChecked.set(true);
    }

    private void hookUpDependencies() {
        isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
            @Override
            public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
                updateDependentViews();
            }
        });
    }

    public void updateDependentViews() {
        if (isExistingUserChecked.get()) {
            emailBlockVisibility.set(View.GONE);
            loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
        }
        else {
            emailBlockVisibility.set(View.VISIBLE);
            loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
        }
    }

    public void loadAsync() {
        new AsyncTask() {
            @Override
            protected Void doInBackground(Void... params) {
                // Simulating some asynchronous task fetching data from a remote server
                try {Thread.sleep(2000);} catch (Exception ex) {};
                numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));
                mIsLoaded = true;
                return null;
            }
        }.execute((Void) null);
    }

    public void logInClicked() {
        // Illustrating the need for calling back to the view though testable interfaces.
        if (isExistingUserChecked.get()) {
            mView.showShortToast("Invalid username or password");
        }
        else {
            mView.showShortToast("Please enter a valid email address");
        }
    }
}

Android developer at VG


14 comments

  • Hannes Dorfmann

    Great explanation. Actually, I do have plans to support MVVM in Mosby as well, but I'm not sure whether I should user androids databinding or RxAndroid. Any thought on this?

    RxAndroid requires writing a lot of boilerplate code (this may change in the future since RxAndroid gets rebuild from scratch).
    android databinding doesn't be perfect either.

    https://github.com/sockeqwe/mosby/issues/71


    • Frode Nilsen

      Frode Nilsen

      Great to hear you have plans to support this! As for Android's or RxAndroid's I'd recommend prioritizing Android's. First and foremost because it is a product of Google and thus is a part of "Core Android" (although it is a support library). Less dependencies from your part. I don't have enough knowledge of RxAndroid's databinding to say anything about the technical advantage/disadvantages in comparison to Android's databinding. I have used RxAndroid in general though and although I like the power of it, it is a bit of a learning curve for new developers. We can continue the discussion in your issue at github!

  • Kiran Rao

    Great article and it does a good job of helping people familiar with MVP understand what it takes to transition to MVVM (although both of those can be used together - for example in VIPER or its derivatives).

    I also agree with your comments about avoiding use of Resources class in your ViewModel (https://github.com/Nilzor/mvp-to-mvvm-transition/blob/mvvm/app/src/main/java/com/nilzor/presenterexample/MainModel.java#L21). I prefer to just return the resource IDs instead from the ViewModel. The your.package.R class is generated at compile time and hence is available in local JUnit tests.

    The Google engineers behind the data binding framework seem to agree - https://code.google.com/p/android/issues/detail?id=178595

    Your comment about using testable interfaces to communicate back with the Views also resonates with me - https://github.com/Nilzor/mvp-to-mvvm-transition/blob/mvvm/app/src/main/java/com/nilzor/presenterexample/MainModel.java#L69

    Prior to the data-binding library, I tried kept my ViewModels clear of any android-related code *and* had a one-way dependency. The View depends on the ViewModel but not the other way round. So, in fact, it might even make sense to move the contents of loginClicked() to the View layer (Activity/Fragment).

    In particular, the clickListener - https://github.com/Nilzor/mvp-to-mvvm-transition/blob/mvvm/app/src/main/java/com/nilzor/presenterexample/MainActivityFragment.java#L34 could be come

    if(mViewModel.isExistingUser()){
    showShortToast("Invalid username");
    } else {
    showShortToast("Please enter a valid email address");
    }


  • Fabio Collini

    Great article!
    I think Android Data Binding will change the way we organize code, it's very powerful. Code in xml is not testable but you can minimize it using a ViewModel or some custom bindings.
    In my article on medium https://medium.com/@fabioCollini/android-data-binding-f9f9d3afc761 I try to use it to create a two-way binding in a form similar to the one you implemented, using custom bindings it's possible to add listeners in an easy way.


  • Davide Schembari

    Hi,
    I'm really hopeful about MVVM through DataBinding.
    I would you think the interaction between a ViewModel and the View when there's an Adapter containing the data? Or could we think of an Adapter as a ViewModel in its own right?


  • Frode Nilsen

    Are you talking about Adapters as in ListAdapters for ListViews? If so, the adapters themselves should never contain data - the ViewModels does. . The RecyclerView has taken over for ListView and I've seen one guy make a RecyclerView which automatically binds to ViewModels: https://github.com/radzio/android-data-binding-recyclerview


  • Mike

    Thanks for this really concise and simple explanation!


  • Suresh

    Thanks for this. It was really helpful. But now I am stuck in a different problem. i have 5 EditText fields. I want other Edittext fields to automatically update depending on the input on first EditText.
    for example:
    if the input in first EditText1 field is 2, then the values in Other EditText field should automatically update as:

    EditText2 : same as the value of EditText1. ie, 2

    EditText3 : 4 (double of EditText1)

    EditText4 : 6 (sum of EditText2 and EditText3)

    Is there any way i can perform it with Android Data Binding approach? or is there any easier approach.

    Thanks,


  • Zeeshan A Zakaria

    Very helpful blog.


  • Erik caffrey

    Awesome article thanks for your time , I’ve built an example hope it will help someone https://github.com/erikcaffrey/People-MVVM.


  • Vesper

    Thinks your great post! However, I still don't understand the reason that don't have a reference of Context in ViewModel?


  • Svetlozar Kostadinov

    I think your MVVM example is actually the PM pattern, because there is no Model object used and your ViewModel serves for this purpose.


    • Frode Nilsen

      Frode Nilsen

      WHat's the "PM" pattern? P=Presenter? Anyway, I did not include a model to keep the example simpler. In a real world scenario this pattern would soon turn into MVVM pattern. For instance there could be a ORM creating a Model object for the User object, or there could be a Model object representing the server-side representation of the User coming from a REST service.

Leave your comment