如何使用Android的架构组件完成ViewModel的活动?

时间:2018-03-02 21:33:15

标签: android android-architecture-components

我试图弄清楚如何以最佳方式完成ViewModel中的Activity。我找到了使用LiveData对象并发出“信号”的一种方法。

我怀疑这个解决方案有一个开销。那么它是正确的解决方案还是我应该使用更准确?

所以转到示例:让我们假设在一个应用程序中是一个活动MainActivity和视图模型,如下所示:

 class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

        model.shouldCloseLiveData.observe(this, Observer { finish() })

    }
 }

并且作为MainActivity的伴侣是一个MainViewModel,如下所示:

class MainViewModel(app: Application) : AndroidViewModel(app) {

  val shouldCloseLiveData = MutableLiveData<Void>()

  fun someAction(){
    shouldCloseLiveData.postValue(null)
  }

}

5 个答案:

答案 0 :(得分:3)

我同意你的感受,这个解决方案看起来并不整洁有两个原因。 首先使用MutableLiveData对象来表示事件是一种解决方法。没有数据被更改。 第二次将LiveData暴露给视图模型的外部一般违反了封装的原则。

我仍然对这个丑陋的android概念感到惊讶。他们应该提供观察视图模型的选项,而不是它的内部LiveData对象。

我尝试用WeakReference来实现观察者模式。这很不稳定。以不可预测的方式,WeakReference的指示对象丢失了(null),在这种情况下,无法调用finish()。这是令人惊讶的,因为我不认为活动是在跑步时收集垃圾。

所以这是排除部分答案。实施为WeakReference的观察者模式似乎无法替代您的建议。

如果我在onStop()onDestroy()期间删除了引用,那么通过硬引用实现观察者模式是否合法。我问了这个问题here

答案 1 :(得分:2)

我同意这似乎不是一个好的解决方案,您的建议确实很好。但我建议以下。

由于您正在使用Kotlin,因此可以将活动中的函数传递给视图模型,如下所示:

ViewModel:

class MainViewModel(app: Application) : AndroidViewModel(app) {

    fun someAction(block: () -> Unit) {
        // do stuff
        block()
    }
}

活动:此处以按钮(和clicklistener)为例,但这可以在活动代码中的任何位置。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

        myButton.setOnClickListener {
            model.someAction() {
                finish()
            }
        }
    }
}

block函数实际上将充当回调。

答案 2 :(得分:2)

您可以使用单个事件,您可以在此处看到实现: https://gist.github.com/JoseAlcerreca/5b661f1800e1e654f07cc54fe87441af#file-event-kt

因此,您无需更改活动的实施

let event: any = {
    target : {
        getBoundingClientRect : () => {
            return {
            
            };
        }
    }
};

视图模型将像

 class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val model = ViewModelProviders.of(this).get(MainViewModel::class.java)

        model.shouldCloseLiveData.observe(this, Observer { finish() })

    }
 }

答案 3 :(得分:0)

我有一个类似的问题:我有两个活动(A和B),其视图模型连接到对象(数据库中的表):从可观察的实时数据中,我必须导航到另一个活动B(从A到B)。问题是,在调用新的活动B之后,B中的可观察对象更改了被观察对象中的值。活动A仍然存在,  其实时数据再次在无限循环中调用B ..的导航代码。

经过研究,我意识到运行finish方法并不意味着活动实际上是destroyed

解决方案是,通过可观察的代码从实时数据中删除与特定活动相关的可观察的事物。

liveData.removeObservers(activity);

我在下面的代码段中显示了它。它是用Java编写的,但是我认为您阅读它没有问题。这样,我解决了我的问题。

public class LockActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...    

        mViewModel = ViewModelProviders.of(this).get(ConfigurationViewModel.class);

        LiveData<Configurazione> liveData = mViewModel.getConfiguration();
        liveData.observe(this, config-> {
            // this code will be executed even another activity is in front of
            // screen
            boolean validToken = (config.getToken()!=null);

            if (!tokenValido) {
                intent = LoginActivity.createIntent(this);
            } else {
                intent = MainActivity.createIntent(this);
            }

            // this line remove the observable, so even activity will be destroied with calm, it is not a problem, the code is no more executed
            liveData.removeObservers(this);
        });
    }

    ...
}

我认为您可以轻松地将这种情况适应您的代码。希望对您有所帮助。

答案 4 :(得分:0)

单行方法

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>

        <import type="android.app.Activity" />

        <import type="androidx.databinding.DataBindingUtil" />

        <import type="androidx.databinding.ViewDataBinding" />
    </data>

    <LinearLayout 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:orientation="vertical"
        tools:context=".MainActivity">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{(v)->((Activity)(((ViewDataBinding)DataBindingUtil.findBinding(v)).lifecycleOwner)).finish()}"
            android:text="Exit" />
    </LinearLayout>
</layout>

或简化布局表达,将代码移至帮助程序类

public class DataBindingHelper {
    public static Activity findActivity(View v) {
        final ViewDataBinding binding = DataBindingUtil.findBinding(v);
        return binding != null ? (Activity) binding.getLifecycleOwner() : null;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>

        <import type="com.xxx.settingitemmoretest.DataBindingHelper" />

        <import type="android.app.Activity" />

        <variable
            name="viewModel"
            type="com.xxx.settingitemmoretest.MainActivity.ViewModel" />

    </data>

    <LinearLayout 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:orientation="vertical"
        tools:context=".MainActivity">


        <ToggleButton
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:checked="@{viewModel.shouldCloseLiveData}"
            android:onCheckedChanged="@{(v, p)-> p ? DataBindingHelper.findActivity(v).finish(): void}"
            android:text="Stub Useless Button"
            android:visibility="gone" />

        <ToggleButton
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:checked="@{viewModel.shouldCloseLiveData}"
            android:onCheckedChanged="@{(v, p)-> p ? ((Activity)context).finish(): void}"
            android:text="Stub Useless Button"
            android:visibility="gone" />
    </LinearLayout>
</layout>

或使用绑定适配器

public class ViewGroupBindingAdapter {

    @BindingAdapter({"android:shouldClose"})
    public static void setShouldClose(ViewGroup viewGroup, boolean shouldClose) {
        if(shouldClose){
            final ViewDataBinding binding = DataBindingUtil.getBinding(viewGroup);
            if (binding != null) {
                ((Activity) binding.getLifecycleOwner()).finish();
            }
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>

        <variable
            name="viewModel"
            type="com.xxx.settingitemmoretest.MainActivity.ViewModel" />

    </data>

    <LinearLayout 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:orientation="vertical"
        android:shouldClose="@{viewModel.shouldCloseLiveData}"
        tools:context=".MainActivity">
     </LinearLayout>
</layout>