Reputation:
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
Reputation: 91
if you want to update data from activity and get response in fragment with dependency injection (KOIN) use shareViewModel in fragment.
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
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
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