user10018802
user10018802

Reputation:

How to update Viewmodels fragments in MainActivity?

I have 3 same fragments in a ViewPager that each contain a ViewModel where I observe for changes on the fragment whenever the list is changed.

Here's the observer;

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

Now in the MainActivity there's a drawer that contains platforms to filter from e.g PC, Xbox and PS4, and when the user closes the drawer I want all of my 3 fragments to update gracefully, is this possible to accomplish with viewmodel? Like how can I observer for list changes and for platforms changes? The drawer in Main Activity adds the selected platforms to a list of Integer e.g PS4 has id of 3

My ViewModel:

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

Upvotes: 0

Views: 2687

Answers (3)

Hasnat Ahmad
Hasnat Ahmad

Reputation: 91

if you want to update data from activity and get response in fragment with dependency injection (KOIN) use shareViewModel in fragment.

share data between fragments

First, we will create a class SharedViewModel.

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

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

Now, we are going to create two Fragments:

MessageReceiverFragment: This Fragment is going to receive the message which will be sent by MessageSenderFragment. It will have a TextView which will show the received message. MessageSenderFragment: This Fragment is going to send the message which will be received by MessageReceiverFragment. It will have a Button to send the message. MessageReceiverFragment class:

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 layout:

<?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:

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 layout:

<?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>

Our MainActivity Class.

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

Now, you are ready to run the app, see if it is working as expected. It should work.

Let's discuss how it is working:

Here, we have created an activity that consists of two fragments. The same activity is the host for both the fragment. In both the fragment, we have created the object of SharedViewModel which is the same object as we are using the same single activity as an owner. This is the reason it is shared. Notice that we have used the requireActivity().

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

In the MessageSenderFragment, we are sending the message on button click, which sets the value of message - LiveData in the SharedViewModel.

Then, in the MessageReceiverFragment, we are observing the change in the message - LiveData, and whenever the message is coming, we are updating the data in textView.

Upvotes: 2

Jeel Vankhede
Jeel Vankhede

Reputation: 12118

What all you can do is, if you want to share ViewModel from activity and reuse it in fragment using it's activity context.

So, on Activity level use like below :

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

And when your drawer closed event callled, then use this mReleasesViewModel object to call your load method :

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

Now, for Fragment level use the same object of ViewModel used on activity, with it's context like below :

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

Rest of observer call will be same on fragment as below :

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

Let me know if still need any detailed explanation, i'll edit my answer accordingly.

Upvotes: 2

Binary Baba
Binary Baba

Reputation: 2083

Have only 1 ViewModel created at an Activity level and pass it to your fragments. Your ViewModel can have 3 LiveData serving for each of your fragments. let's assume you are showing upcoming releases in one fragment and already released games in another. So just like you did for upcoming releases, create another method in your ViewModel and return a LiveData which will hold the list of released games.

In the below code mViewModel will be the passed view model to your fragments.

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

Upvotes: 2

Related Questions