【问题标题】:Get an activity's context from view model class从视图模型类中获取活动的上下文
【发布时间】:2019-02-01 13:59:26
【问题描述】:

我的代码基于我发现的一个使用 Android 架构组件和数据绑定的示例。这对我来说是一种新的方式,它的编码方式使得很难用被点击的帖子的信息正确打开一个新的活动。

这是帖子的适配器

class PostListAdapter : RecyclerView.Adapter<PostListAdapter.ViewHolder>() {
    private lateinit var posts: List<Post>

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostListAdapter.ViewHolder {
        val binding: ItemPostBinding = DataBindingUtil.inflate(
            LayoutInflater.from(parent.context),
            R.layout.item_post,
            parent, false
        )

        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: PostListAdapter.ViewHolder, position: Int) {
        holder.bind(posts[position])
    }

    override fun getItemCount(): Int {
        return if (::posts.isInitialized) posts.size else 0
    }

    fun updatePostList(posts: List<Post>) {
        this.posts = posts
        notifyDataSetChanged()
    }

    inner class ViewHolder(private val binding: ItemPostBinding) : RecyclerView.ViewHolder(binding.root) {
        private val viewModel = PostViewModel()

        fun bind(post: Post) {
            viewModel.bind(post)
            binding.viewModel = viewModel
        }
    }
}

bind 方法来自视图模型类:

class PostViewModel : BaseViewModel() {
    private val image = MutableLiveData<String>()
    private val title = MutableLiveData<String>()
    private val body = MutableLiveData<String>()

    fun bind(post: Post) {
        image.value = post.image
        title.value = post.title
        body.value = post.body
    }

    fun getImage(): MutableLiveData<String> {
        return image
    }

    fun getTitle(): MutableLiveData<String> {
        return title
    }

    fun getBody(): MutableLiveData<String> {
        return body
    }

    fun onClickPost() {
        // Initialize new activity from here, perhaps?
    }
}

在布局 XML 中,设置 onClick 属性

android:onClick="@{() -> viewModel.onClickPost()}"

指向这个onClickPost 方法确实有效,但我无法从那里初始化Intent。我尝试了很多方法来获取MainActivitiy的上下文,但都没有成功,比如

val intent = Intent(MainActivity::getApplicationContext, PostDetailActivity::class.java)

但它会按时显示错误。

【问题讨论】:

  • ViewModel 不应该知道上下文或任何关于 Android 的信息。所以我猜想视图需要订阅一个事件或者当调用 onClickPost 方法时 ViewModel 发出的东西。但是我面临着类似的问题,所以我对正确的答案很感兴趣。
  • 试试singleliveevent 模式
  • @MidasLefko,似乎是这样,但我遇到了 ViewModelFactory 不灵活和动态接受不止一种类型的 ViewModel 的问题。
  • @gamofe 这听起来像是一个新问题.. .
  • 它死了。我确实创建了一个新的stackoverflow.com/questions/52033403/…

标签: android mvvm android-recyclerview android-databinding android-viewmodel


【解决方案1】:

试试:android:onClick="@{(view) -&gt; viewModel.onClickPost(view)}"

同时更改onClickPost 以获取视图。然后您可以在视图上使用view.getContext() 方法来访问存储在该视图中的上下文。

但是,由于 ViewModel 不应引用视图或任何其他包含 Activity 上下文的类,因此将启动 Activity 的逻辑放在 ViewModel 中是非常不合适的。你绝对应该考虑一个单独的地方这样做。

就我个人而言,如果它是一个没有任何额外包袱的简单 startActivity,我会创建一个单独的类来保存静态方法。通过数据绑定,我将导入该类并在 onClick 中使用它,使用我上面说的方法启动一个新的 Activity。

一个例子:

public class ActivityHandler{        
    public static void showNextActivity(View view, ViewModel viewModel){
        Intent intent = new Intent(); //Create your intent and add extras if needed
        view.getContext().startActivity(intent);
    }
}

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <import type="whatever.you.want.ActivityHandler" />
        <variable name="viewmodel" type="whatever.you.want.here.too.ViewModel" />
    </data>

    <Button
        //Regular layout properties
        android:onClick="@{(view) -> ActivityHandler.showNextActivity(view, viewmodel)}"
        />
</layout>

在此处查看侦听器绑定:https://developer.android.com/topic/libraries/data-binding/expressions#listener_bindings

但是,根据所需的数据量,您可能希望将 startActivity 代码放在最适合您的应用设计的其他类中。

【讨论】:

  • 这样做是不是破坏了 MVVM 模式?
  • 另外,startActivity() 在视图模型中不可用。
  • @Eselfar 是的,我正在编辑我的答案以准确地说出来。
  • @gamofe 可以通过从 View 对象获得的上下文使用 startActivity()view.getContext().startActivity()
  • @RohitSharma 我没有说要在 ViewModel 中使用上下文。我明确表示 ViewModel 不应该使用它,并提供了一个替代解决方案来避免在 ViewModel 中使用它。
【解决方案2】:

尝试使用SingleLiveEvent

这是来自Googles architecture samples repo 的代码(以防它从仓库中删除):

import android.arch.lifecycle.LifecycleOwner;
import android.arch.lifecycle.MutableLiveData;
import android.arch.lifecycle.Observer;
import android.support.annotation.MainThread;
import android.support.annotation.Nullable;
import android.util.Log;

import java.util.concurrent.atomic.AtomicBoolean;

/**
 * A lifecycle-aware observable that sends only new updates after subscription, used for events like
 * navigation and Snackbar messages.
 * <p>
 * This avoids a common problem with events: on configuration change (like rotation) an update
 * can be emitted if the observer is active. This LiveData only calls the observable if there's an
 * explicit call to setValue() or call().
 * <p>
 * Note that only one observer is going to be notified of changes.
 */
public class SingleLiveEvent<T> extends MutableLiveData<T> {

    private static final String TAG = "SingleLiveEvent";

    private final AtomicBoolean mPending = new AtomicBoolean(false);

    @MainThread
    public void observe(LifecycleOwner owner, final Observer<T> observer) {

        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
        }

        // Observe the internal MutableLiveData
        super.observe(owner, new Observer<T>() {
            @Override
            public void onChanged(@Nullable T t) {
                if (mPending.compareAndSet(true, false)) {
                    observer.onChanged(t);
                }
            }
        });
    }

    @MainThread
    public void setValue(@Nullable T t) {
        mPending.set(true);
        super.setValue(t);
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    public void call() {
        setValue(null);
    }
}

【讨论】:

    【解决方案3】:

    您甚至可以将活动实例传递给模型或布局,但我不喜欢这样做。

    首选的方式是将界面传递给行布局。

    在布局数据中声明变量

    <variable
        name="onClickListener"
        type="android.view.View.OnClickListener"/>
    

    点击时调用它

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="@{onClickListener::onClick}"
        >
    

    还从适配器设置此侦听器

     binding.viewModel = viewModel
     binding.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                context.startActivity(new Intent(context, MainActivity.class));
            }
        });
    

    【讨论】:

      猜你喜欢
      • 2014-10-11
      • 1970-01-01
      • 2016-11-24
      • 2019-12-22
      • 2012-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多