Introduction to Android Architecture Components

Put the magic back into Android app development

In Nov 2017 Architecture Components v1 was launched . This is a big deal because for the longest time it was very difficult to deal with certain use cases which would crop up all the time in mobile app development.

Things like dealing with configuration changes when rotating a screen, and sharing app state data between fragments and activities were difficult.

While these difficulties are not insurmountable, the fact that they crop up in so many common scenarios have had a negative impact on the developer experience on Android and didn’t make it feel magical. :angry:

Architecture Components puts the magic back into Android development! :tophat:

What is it?

There are quite a few patterns out there for app architectures, such as MVP, MVVM, and unidirectional. I’m a huge fan ofunidirectional, and I like MVVM too. There are 3 parts of M-V-VM, the Model , View , and ViewModel . Let’s take a look at what each of these are.

View — This is the UI component that is in a layout and rendered on the display.

ViewModelViews subscribe to the data they are interested in from the ViewModel . So when this data changes it can be broadcast to any interested observers.

  • The ViewModel is responsible for preparing data for consumption by the View.
  • The ViewModel’s state is stable across the lifecycle of Activities and Fragments . So as an Activity is torn down and rebuilt (on an orientation change) it can use the same ViewModel . And you can scope the lifetime of these ViewModels to the Activity lifetime as well, so that when the Activity is finished (not destroyed), then the ViewModel can be cleaned up.
  • LiveData is an interesting piece of a ViewModel that you can wrap any data that changes quite rapidly and these changes have to be reflected in UI components.
  • ViewModels should not hold references to any Views . And if they need an ApplicationContext , then you can use AndroidViewModel which supplies this.
  • You don’t directly create a ViewModel , instead you ask the system to find you one. And the system ends up creating the ViewModel and managing what it’s scope is based on this. Basically you have to use a factory method to get a reference to a ViewModel instead of just creating one yourself using a constructor.

Model — This is where your underlying data is actually stored. The model can be backed by local persistence stores, and synchronized with remote data sources. You can use Room to make it easier to work with SQLLite (on device) and have Room generate the ORM layer for you. Or you can use Firebase for persistence, which automagically syncs its state across multiple platforms. You have a lot of flexibility in what you choose. You can even use something like Redux in addition to Firebase to be your Model .


This article is focused on the ViewModel and LiveData parts of the Architecture Components. I will be covering Lifecycle awareness and Models in other articles.

Example

I’ve created a simple app that showcases how you might use a ViewModel and LiveData in your apps. You can get this sample on GitHub .

The sample just has 1 Java file — MainActivity.java . This Activity loads its state from a StateViewModel , which contains two pieces of data. Here’s the entire Activity .

public class MainActivity extends AppCompatActivity {
    private TextView dataTextView;
    private TextView counterTextView;
    private StateViewModel stateViewModel;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        dataTextView = findViewById(R.id.data_textview);
        counterTextView = findViewById(R.id.counter_textview);
        setupModelView();
        attachObservers();
    }

// Deal with loading state from ViewModel
    private void setupModelView() {
        stateViewModel = 
            ViewModelProviders.of(this).get(StateViewModel.class);
            dataTextView.setText(
                String.format(
                "Data: %s", stateViewModel.getData()));
    }

private void attachObservers() {
        stateViewModel
                .getCounter()
                .observe(
                        this,
                        count -> {
                            counterTextView.setText(
                                    String.format("Count: %s",
                                    Long.toString(count)));
                        });
    }
}

Data # 1.There’s a UUID String that is generate the first time this StateViewModel is created and this is displayed in the UI. This String does not change for the lifetime of the Activity . It is stable across configuration changes. So as you rotate the screen, and the Activity is destroyed and then recreated (but not finished), the same UUID String will be displayed in the UI. When you finish the Activity by pressing the back button, or by going to the task switcher and swiping the Activity away, then the ViewModel will be destroyed and onCleared() will be called.

class StateViewModel extends AndroidViewModel {
    private final ScheduledExecutorService myExecutor;
    // This value doesn't change after it is initialized
    private String mData; 
    // This value changes over time
    private CounterLiveData mCounter = new CounterLiveData();

public StateViewModel(Application context) {
        super(context);
        myExecutor = Executors.newSingleThreadScheduledExecutor();
        myExecutor.scheduleWithFixedDelay(
            this::recurringTask, 0, 1, TimeUnit.SECONDS);
        Log.d(
            Tags.viewmodel.name(), 
            "ViewModel constructor: created executor");
    }

// Counter
    public void recurringTask() {
        long counter = mCounter.get();
        Log.d(Tags.viewmodel.name(), 
              counter % 2 == 0 ? "task: tick" : "task: tock");
        mCounter.set(counter + 1);
    }

public CounterLiveData getCounter() {
        return mCounter;
    }

// Data
    public void setData(String mData) {
        this.mData = mData;
    }

public String getData() {
        if (isDataSet()) {
            Toast.makeText(
                getApplication(), 
                "Re-using ViewModel", Toast.LENGTH_SHORT).show();
        } else {
            setData(UUID.randomUUID().toString());
            Toast.makeText(
                getApplication(), 
                "This is a new ViewModel", 
                Toast.LENGTH_SHORT).show();
        }
        return mData;
    }

public boolean isDataSet() {
        return mData != null;
    }

@Override
    protected void onCleared() {
        super.onCleared();
        myExecutor.shutdown();
        Log.d(Tags.viewmodel.name(), mCounter.toString());
        Log.d(Tags.viewmodel.name(), 
              "onCleared: lifecycle of activity finished");
    }
}

Data # 2.The ViewModel also creates a ScheduledExecutor that runs a simple task every second. This task simply updates a counter, and it generates a log message (“ tick ”, or “ tock ”). This Executor also sets the value of this counter in a CounterLiveData object. The UI actually subscribes to this LiveData object and when it changes the UI gets updated with the current count. This too is stable across configuration changes. When the Activity is finally finished, the onCleared() method actually shuts the executor down. Also, you have to be mindful of which thread the CounterLiveData’s value is set.

class CounterLiveData extends MutableLiveData {
    public String toString() {
        return String.format("count=%d", getValue());
    }

public Long get() {
        return getValue() == null ? 0L : getValue();
    }

public void set(Long value) {
        if (Looper.getMainLooper().equals(Looper.myLooper())) {
            // UI thread
            setValue(value);
        } else {
            // Non UI thread
            postValue(value);
        }
    }
}

Adding Architecture Components to your project

Read more about how to modify your build.gradle file on developer.android.com . Here’s a simple build.gradle file snippet just for Lifecycles , ViewModel , and LiveData .

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    // Support lib
    implementation 'com.android.support:appcompat-v7:27.0.2'

// Lifecycles
    implementation "android.arch.lifecycle:runtime:1.0.3"
    annotationProcessor "android.arch.lifecycle:compiler:1.0.0"
    implementation "android.arch.lifecycle:common-java8:1.0.0"

// ViewModel, LiveData
    implementation "android.arch.lifecycle:extensions:1.0.0"
}

Further exploration

  • Codelab to get started with Android lifecycle-aware components
  • “Model View ViewModel on Android” article on medium
  • “Deep dive into Data Loading with Architecture Components” on medium
  • Tutorial on Architecture Components — part 1 , part 2
稿源:developerlife.com (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 移动开发 » Introduction to Android Architecture Components

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录