Android Data Binding: Goodbye Presenter, Hello ViewModel!

Keywords: Android Attribute xml github

@ author ASCE1885 Github Brief book micro-blog CSDN 
Original link

Recent period MVP mode Already become Android Application Development UI Layer Framework Mainstream trend of design. Similar TED MOSBYnucleus and mortar Such frameworks have introduced Presenters to help us build a concise app architecture. They also help us (to varying degrees) deal with the notorious issues of device rotation and state persistence on Android platforms. MVP patterns also help isolate boilerplate code, although this is not the original design intent of MVP patterns.

On Google I/O 2015, with Android M preview release Data Binding Compatible libraries change all this.

According to Wikipedia's description of MVP entries, Presenter functions as follows:

Presenter acts on the model and view, which retrieves data from the warehouse (model) and formats it for view to display.

The Data Binding framework will take over the main responsibilities of Presenter (acting on models and view s), while the remaining responsibilities of Presenter (fetching data from warehouses and formatting) will be taken over by ViewModel (an enhanced version of Model). ViewModel is a standalone Java Class, whose sole responsibility is to represent data after a View. It can merge data from multiple data sources (Models) and process these data for display. I've written an article before. A short article on ViewModel It talks about it and Data Differences between Models or Transport Model s.

The architecture we're going to talk about today is MVVM( Model-View-ViewModel It was originally proposed by Microsoft in 2005 (don't be scared) as a proven concept. Here I will give an example of the change from MVP to MVVM. Let me steal Hanne Dorfmann's introduction. TED MOSBY framework Illustrations in the article.

You can see that all bindings and updates to data in view are implemented through the Data Binding framework. adopt ObservableField class View reacts when the model changes, and the reference to attributes in the XML file enables the framework to push changes to the corresponding ViewModel when the user operates View. We can also subscribe to changes in the attributes of the code, which allows us to disable TextView, for example, when CheckBox is clicked. One of the great advantages of using standard Java classes to represent the visual state of Views like this is obvious: you can easily unit this visual behavior. test.

The above illustration of MVP has a method called Presenter.loadUsers(), which is a command. These methods are defined in ViewModel in MVVM. As can be seen from Wikipedia articles:

The view model is an abstract view that exposes public attributes and commands.

So this may be a little different from what you used to know. Models in MVP mode are probably just dumb classes for storing data. Don't be afraid of putting business logic in Models or View Models. This is object-oriented programming Core criteria. Back to the Presenter.loadUsers() function, which is now a function placed in the ViewModel, it may be called by the View's code-behind or by the data binding command in the View's XML file. If android-developer-preview problem tracking is in This issue If the problem described is supported. If we can't get the support of data binding to command function, we can only use the previous android:onClick grammar or add listeners to view manually.

code-behind, a Microsoft concept, is often associated with early ASP .NET Or WinForms are linked together. I think it can also be used as a descriptive term on Android. View consists of two elements: View's layout file (XML) and postcode (Java), which usually refers to Fragments, Activities or other classes inherited from View.java.

Processing System Call

View's post-code also needs to complete a series of use cases -- initializing the system, opening dialog functions, or any calls that need to refer to Android Context objects. But don't put such code calls in ViewModel. If ViewModel contains

import android.content.Context;
  • 1
  • 1

This code shows that you are using it incorrectly. Don't do it. Curiosity kills the cat.

I haven't fully decided on the best way to solve this problem, but it's because there are several good choices. One way is to save the presenter element in Mosby by holding a reference to View in the ViewModel. This solution does not reduce testability. But unlike having a separate Presenter class in Mosby, I insist that viewing as a concrete implementation of the interface can simplify the code. Another approach might be to use Otto of Square Event bus mechanisms like this to initialize similar

new ShowToastMessage("hello world")
  • 1
  • 1

Command. This would be a good separation of view and view model, but is that a good thing?

Don't we need a framework?

Has the Data Binding framework taken over the work of frameworks like Mosby or Mortar? It's just part of it. What I want to see is that these frameworks evolve or add new branches to MVVM-type frameworks so that we can make full use of Data Binding while minimizing dependence on third-party frameworks and keeping them small and beautiful. Although the Presenter era may be over, these frameworks still play a role in managing the declaration cycle and state persistence of view (or ViewModel), which has not changed. (If Google introduces a LifeCycleAffected interface for Fragment, Activity) What a cool thing to do with View! This interface consists of a function called addOnPauseListener() and addOnResumeListener(), and how to use this interface in our example will be left to you to implement.)

Update: Recently learned Android ViewModel Framework It may actually be very suitable for MVVM and Android's DataBinding. But I haven't had time to try it yet.

summary

When I first heard that Android M was committed to improving SDK and focused on developers, I was really excited. When I heard that they had introduced Data Binding, I was shocked. I have used Data Binding technology for several years on other platforms such as WinForms, WPF, Silverlight and Windows Phone. I know that this can help us write concise architecture and fewer boilerplate code. This framework is on the developer's side, not on our side, which I felt a long time ago.

But Data Binding is not a silver bullet. It has its drawbacks. Defining bindings in an XML file is a problem in itself. XML is not compiled, nor can it be unit tested. So you will often find errors at runtime, not during compilation. Forget to bind properties to View? Unfortunately. But tools can help a lot -- that's why I want Google to make Android Studio as supportive of Data Binding as possible. XML binding syntax and reference checking, automatic completion and navigation support. Renaming support for XML fields. From my test of Android Studio 1.3 beta, I'm at least sure they're thinking about it. Some features are already supported, but many are not yet supported, but version 1.3 is still in beta stage, and we can expect more.

Code example

Next, I'll give an example to demonstrate the results of migrating from MVP architecture to MVVM architecture. In the MVP version project, I used the Mosby framework and Butterknife to implement view injection. In the M VVM example, I used Android M Data Binding and removed the dependencies on Mosby and Butterknife in the project. As a result, Presenter can be lost, and the code in Fragment is reduced, but ViewModel takes over a lot of code.

In this example, I directly refer to View to generate toast messages. This may not be a method I will advocate in the future, but in theory, there is no problem in doing so. Using Robolectric and Mockkito to mock Fragment s is testable and does not leak memory unless you incorrectly refer to ViewModels.

The app below is just a demonstration. It has a simple landing page, some asynchronous data will be loaded in the background, and there will be some dependencies between views.

If you want to read the code in Android Studio, you can go to On Github The labels of MVP and MVVM were detected respectively.

Now get ready for code bombardment.

MVP – VIEW – XML

<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124

MVP – VIEW – JAVA

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();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

MVP – PRESENTER

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);
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

MVP – MODEL

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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

MVVM – VIEW – XML

<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131

MVVM – VIEW – JAVA

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();
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

MVVM – VIEWMODEL

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");
        }
    }
}

Posted by t31os on Thu, 21 Mar 2019 21:42:52 -0700