【问题标题】:How to update Viewmodels fragments in MainActivity?如何更新 MainActivity 中的 Viewmodels 片段?
【发布时间】:2018-11-03 05:27:54
【问题描述】:

我在 ViewPager 中有 3 个相同的片段,每个片段都包含一个 ViewModel,每当列表发生更改时,我都会在其中观察片段的更改。

这是观察者;

    mReleasesViewModel = ViewModelProviders.of(this, new ReleasesViewModelFactory(filter)).get(ReleasesViewModel.class);
    // livedata
    mDatabaseLoading.setVisibility(View.VISIBLE);
    mReleasesViewModel.getUpcomingReleases().observe(this, new Observer<List<_Release>>() {
        @Override
        public void onChanged(@Nullable List<_Release> releases) {
            // whenever the list is changed
            if (releases != null) {
                mUpcomingGamesAdapter.setData(releases);
                mUpcomingGamesAdapter.notifyDataSetChanged();
            }
            mDatabaseLoading.setVisibility(View.GONE);
        }
    }); 

现在在 MainActivity 中有一个抽屉,其中包含要从例如 PC、Xbox 和 PS4 过滤的平台,当用户关闭抽屉时,我希望我的所有 3 个片段都能正常更新,这可以通过 viewmodel 完成吗?比如我如何观察列表变化和平台变化? Main Activity 中的抽屉将所选平台添加到 Integer 列表中,例如 PS4 的 id 为 3

我的视图模型:

public class ReleasesViewModel extends ViewModel {
    public static final String TAG = ViewModel.class.getSimpleName();
    // Example: Whenever the value contained by this MutableLiveData changes, we will set the contained value to our TextView
    // Livedata objects will usually be kept in the ViewModel class
    private MutableLiveData<List<_Release>> upcomingReleases;
    // this field [mMonthYearFilter] is passed by our fragment to the AndroidViewModel
    // to pass additional argument to my custom AndroidViewModel I need a AndroidViewModelFactory
    private String monthYearFilter;
    private ReleasesRepository releasesRepository;

    public ReleasesViewModel(String monthYearFilter) {
        this.monthYearFilter = monthYearFilter;
    }

    public MutableLiveData<List<_Release>> getUpcomingReleases() {
        if (upcomingReleases == null) {

            upcomingReleases = new MutableLiveData<>();

            // User settings region & platforms
            String region = SharedPrefManager.read(SharedPrefManager.KEY_PREF_REGION, "North America");
            Set<String> defaultPlatformsSet = new HashSet<>();
            ArrayList<Integer> platforms = SharedPrefManager.read(SharedPrefManager.PLATFORM_IDS, defaultPlatformsSet);

            releasesRepository = new ReleasesRepository(region, monthYearFilter, platforms);
            loadReleases();
        }
        return upcomingReleases;
    }


    private void loadReleases() {
        releasesRepository.addListener(new FirebaseDatabaseRepository.FirebaseDatabaseRepositoryCallback<_Release>() {
            @Override
            public void onSuccess(List<_Release> result) {
                upcomingReleases.setValue(result);
            }

            @Override
            public void onError(Exception e) {
                Log.e(TAG, e.getMessage());
                upcomingReleases.setValue(null);
            }
        });
    }
}

【问题讨论】:

  • 对于这种情况,您应该在活动级别而不是在片段实例中初始化ViewModel,并通过它的activity上下文在片段中使用它,它将在片段之间共享ViewModel实例。
  • 我很难想象这个,这是否意味着我只需要一个视图模型?因为这三个片段中的每一个都有不同的过滤器,它们不会共享相同的数据
  • 你能用更多的片段和活动代码解释一下你的用例吗?
  • 我更新了我的问题并添加了我的 ViewModel,我的活动和片段就像没有任何重要的代码一样基本,我只想能够更新我的所有片段MainActivity 抽屉已关闭

标签: android android-fragments viewmodel


【解决方案1】:

在 Activity 级别仅创建 1 个ViewModel 并将其传递给您的片段。您的 ViewModel 可以为您的每个片段提供 3 个 LiveData 服务。假设您在一个片段中展示即将发布的版本,而在另一个片段中展示已经发布的游戏。因此,就像您为即将发布的版本所做的那样,在您的 ViewModel 中创建另一个方法并返回一个 LiveData,它将保存已发布游戏的列表。

在下面的代码中,mViewModel 将是传递给片段的视图模型。

mViewModel.getReleasedGames().observe(this, new Observer<List<_Release>>() {
        @Override
        public void onChanged(@Nullable List<_Release> released_games) {
            // whenever the list is changed
            if (released_games != null) {
                mReleasedGamesAdapter.setData(releases);
                mReleasedGamesAdapter.notifyDataSetChanged();
            }
            mDatabaseLoading.setVisibility(View.GONE);
        }
    }); 

【讨论】:

  • 谢谢,这是否意味着我的 ViewModel 中会有我的 3 个片段的列表?
  • 是的,您可以在视图模型中为每个片段提供 3 个不同的列表。
  • 如果我的视图模型中有 3 个列表,这不是不好的做法吗?当我使用 getReleasedGames() 方法获取列表时,该方法向我的 firebase 数据库发送请求
  • 设计实践仅在其用途方面是不好的。在您的用户案例中,由于您的 3 个片段共存在一起,因此您无论如何都必须同时在某个地方维护 3 个列表。通过让 ViewModel 的生命周期与活动相关联,您可以确保列表数据将一直保留到活动被销毁。鉴于您使用的是 ViewPager,在滑动时可能会重新创建片段,但您不希望丢失获取的数据,因此最好在 ViewModel 中维护列表。
  • 让我知道这是否有意义。
【解决方案2】:

如果您想从活动中分享ViewModel,您所能做的就是 并使用它的活动 context 在片段中重用它。

所以,在 Activity 级别 使用如下:

mReleasesViewModel = ViewModelProviders.of(this, new ReleasesViewModelFactory(filter)).get(ReleasesViewModel.class); // this will make ViewModel on activity level.

当你的抽屉关闭事件被调用时,然后使用这个mReleasesViewModel 对象来调用你的加载方法:

mReleasesViewModel.loadReleases(); // This will provide your list data to your LiveData object while observing on fragments.

现在,对于 Fragment 级别,使用在 Activity 上使用的相同 ViewModel 对象,其 context 如下所示:

mReleasesViewModel = ViewModelProviders.of(getActivity()).get(ReleasesViewModel.class); // this will make same ViewModel object on fragment level as activity.

观察者调用的其余部分将与以下片段相同:

mReleasesViewModel.getUpcomingReleases().observe(this, new Observer<List<_Release>>() {
    @Override
    public void onChanged(@Nullable List<_Release> releases) {
        // whenever the list is changed
        if (releases != null) {
            mUpcomingGamesAdapter.setData(releases);
            mUpcomingGamesAdapter.notifyDataSetChanged();
        }
        mDatabaseLoading.setVisibility(View.GONE);
    }
});

如果还需要任何详细解释,请告诉我,我会相应地编辑我的答案。

【讨论】:

  • 谢谢,但您能否编辑您的答案以反映 loadReleases() 方法的内容?这是我唯一不明白的事情
【解决方案3】:

如果您想通过依赖注入 (KOIN) 从 Activity 更新数据并在 Fragment 中获取响应,请在 Fragment 中使用 shareViewModel。

share data between fragments

首先,我们将创建一个 SharedViewModel 类。

class SharedViewModel : ViewModel() {
    val message = MutableLiveData<String>()

    fun sendMessage(text: String) {
        message.value = text
    }
}

现在,我们将创建两个片段:

MessageReceiverFragment:该Fragment 将接收MessageSenderFragment 发送的消息。它将有一个 TextView 将显示收到的消息。 MessageSenderFragment:此 Fragment 将发送将由 MessageReceiverFragment 接收的消息。它将有一个按钮来发送消息。 MessageReceiverFragment 类:

class MessageReceiverFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_receiver, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val model = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
        model.message.observe(viewLifecycleOwner, Observer {
            textViewReceiver.text = it
        })
    }
}

fragment_receiver 布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textViewReceiver"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send Your Message"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

MessageSenderFragment 类:

class MessageSenderFragment : Fragment() {
    lateinit var model: SharedViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_sender, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model = ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)
        button.setOnClickListener { model.sendMessage("MindOrks") }
    }
}

fragment_sender 布局:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send Your Message"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Now, let's update the activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/receiverFragment"
        android:name="com.mindorks.sharedviewmodelsample.MessageReceiverFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toTopOf="@+id/senderFragment"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <fragment
        android:id="@+id/senderFragment"
        android:name="com.mindorks.sharedviewmodelsample.MessageSenderFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/receiverFragment" />


</androidx.constraintlayout.widget.ConstraintLayout>

我们的 MainActivity 类。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

现在,您已准备好运行应用程序,看看它是否按预期工作。它应该可以工作。

让我们讨论一下它是如何工作的:

在这里,我们创建了一个由两个片段组成的活动。相同的活动是两个片段的主机。 在这两个片段中,我们创建了 SharedViewModel 的对象,它与我们使用相同的单个活动作为所有者相同的对象。这就是它被共享的原因。请注意,我们使用了 requireActivity()。

ViewModelProvider(requireActivity()).get(SharedViewModel::class.java)

在 MessageSenderFragment 中,我们在单击按钮时发送消息,这会设置消息的值 - SharedViewModel 中的 LiveData。

然后,在 MessageReceiverFragment 中,我们正在观察消息的变化 - LiveData,每当消息到来时,我们都会更新 textView 中的数据。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-23
    • 1970-01-01
    • 2017-05-10
    • 2019-11-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多