let say for example I have 1 Activity that contains 5 Fragments and those Fragments presents a 1 flow of payment process so each Fragment depends on the previous Fragment by passing data of what the user chooses
I'm planning to make 1 ViewModel in the Activity that handles the data between fragments but I've read that it is a bad idea to expose MutableLiveData outside of the view model. so I can't say viewModel.setdata(example) in the Activity the best solution was is to use navigation component with safe args and create a ViewModel for each Fragment and create ViewModelFactory for each fragment too.
but this will make me write too many classes.
is there an optimal way to pass data between views using 1 ViewModel without violating the MVVM architecture rules?
Yes, this is good decision to use ViewModel to share data between fragments. Look this
SharedViewModel
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
MasterFragment
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
DetailFragment
public class DetailFragment extends Fragment {
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
model.getSelected().observe(getViewLifecycleOwner(), item -> {
// Update the UI.
});
}
}
Here is the black magic:
val viewModel: MyViewModel by activityViewModels()
Related
I wondered if it's possible to pass a String data which has declared in Activity class and pass the String data to ViewModel class then pass the data to Fragment class.
ViewModel Class
class TimeTableViewModel extends ViewModel {
private MutableLiveData<String> start_time_str = new MutableLiveData<>();
void send_StartTime(String start_Time){
start_time_str.setValue(start_Time);
}
LiveData<String> get_StartTime(){
return start_time_str;
}}
In ViewModel Class, I have MutableLiveData<String> start_time_str and it has been initialized as new MutableLiveData<>();
I would like to use void send_StartTime(String start_Time) function in Activity class to set value of argument String start_Time and call the start_time_str in Fragment class.
Activity Class
#Override
public boolean onOptionsItemSelected(MenuItem item){
switch (item.getItemId()){
case android.R.id.home:
finish();
break;
case R.id.add_schedule_save:
String start_time_str = startTime.getText().toString();
Intent intent_restart0 = new Intent(TimeTable_Add_New_Schedule.this, MainActivity.class);
startActivity(intent_restart0);
TimeTableViewModel timeTableViewModel = new TimeTableViewModel();
timeTableViewModel.send_StartTime(start_time_str);
Toast.makeText(this,""+start_time_str,Toast.LENGTH_LONG).show();
break;
}
return super.onOptionsItemSelected(item);
}
Fragment Class
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
TimeTableViewModel timeTableViewModel = new TimeTableViewModel();
timeTableViewModel.get_StartTime().observe(getViewLifecycleOwner(), new Observer<String>() {
#Override
public void onChanged(String s) {
mon_textView_11.setText(s);
}
});
}
In the Fragment class I call get_StartTime() function to get start_time_str and set the String value to my TextView. I think the start_time_str has been successfully set by function of timeTableViewModel.send_StartTime(start_time_str); in the Activity Class because of Toast.maketext is worked like a charm. However the TextView is not shown anything. I have tested Text Color is not white so that if the string value is correctly called, it should be appear on screen. If you have any suggestions, I would love to hear your advice.
Thank you very much.
It really depends on how do you create your ViewModel instance. Now you are creating ViewModel by its constructor, but that is not a proper way. You should use ViewModelProvider or extension methods that were created by Google team.
If you go with ViewModelProvider you should do it like this:
TimeTableViewModel viewModel = new ViewModelProvider(this).get(TimeTableViewModel.class);
It is important to pass the correct context to ViewModelProvider constructor call. If you are in fragment and you will just use getContext() instead of getActivity(), you will not get the same instance as it was created in Activity. You will create a new instance of ViewModel, that will be scoped only inside of fragment lifecycle. So it is important to use in both parts activity context to get the same instance.
Activity part:
TimeTableViewModel viewModel = new ViewModelProvider(this).get(TimeTableViewModel.class);
Fragment part:
TimeTableViewModel viewModel = new ViewModelProvider(getActivity()).get(TimeTableViewModel.class);
Is important that your fragment is located inside the same activity that is using this ViewModel.
But guys at Google has make it easier for us with some extension methods. But as far as I know, they are working only in Kotlin classes. So if you have Kotlin code, you can declare your ViewModel simply like this:
private val quizViewModel: TimeTableViewModel by activityViewModels()
For Fragment scoped ViewModel you need to write something like this:
private val quizViewModel: TimeTableViewModel by viewModels()
But you have to add Kotlin ktx dependency to your project build.gradle file. For example like this:
implementation 'androidx.fragment:fragment-ktx:1.1.0'
If you are using Android Architecture and want to share activityViewModel in your fragments.
To get viewModels in fragment use below code:
private val fragmentViewModel: Fragment1ViewModel by viewModels()
private val activityViewModel: MainActivityViewModel by activityViewModels()
and in MainActivity use below code:
private val activityViewModel: MainActivityViewModel by viewModels()
I'm using kotlin language
I'm used same as tutorial but not working
Here's example codes how to declare my ViewModel
add dependencies in build.gradle:
implementation 'androidx.fragment:fragment-ktx:1.4.1'
example of Activity class
class HomepageActivity : AppCompatActivity() {
private lateinit var sharedViewModel: SharedViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityHomepageBinding.inflate(layoutInflater)
setContentView(binding.root)
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
sharedViewModel.isMenuOpen.observe(this, {
onMenuOpen(it)
})
}
example in Fragment class
class HomeFragment : Fragment() {
private lateinit var sharedViewModel: SharedViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
sharedViewModel = activity!!.run{
ViewModelProvider(this).get(SharedViewModel::class.java)
}
}
I found the simplest fix to this problem, instead of giving the owner as "this" change it to getActivity().
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (mvm == null) {
mvm = new ViewModelProvider(getActivity()).get(CounterFgViewModel.class);
}
}
I just started using the architecture component of Android in my app, and I have a use case when:
A fragment is an observer of ViewModel's LiveData (which comes from a Room request)
This fragment starts an activity at some point
The activity needs to use the same data (as LiveData in the fragment) and update it
The user then returns to the fragment
I tried using the same ViewModel class and adding the fragment and activity as observers, thinking that somehow updating from the activity will affect the fragment when returning to it, but it doesn't work.
The two ViewModels seem independent.
How can I force refresh the fragment data when returning from the activity (in onActivityResult for example)?
Or is it possible to share the same ViewModel between the fragment and activity? (though I doubt it since the ViewModel is linked to the lifecycle of the observer)
Fragment
public void onAttach(#NonNull Context context) {
...
mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
mViewModel.getData().observe(this, new Observer<List<Data>>() {
#Override
public void onChanged(List<Data> data) {
mAdapter.setData(data);
}
});
}
// in an onClick method
startActivity(intent); // go to the Activity
Activity
protected void onCreate(Bundle savedInstanceState) {
...
mViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
mViewModel.getData().observe(this, new Observer<List<Data>>() {
#Override
public void onChanged(List<Data> data) {
mPagerAdapter.setData(data);
}
});
}
Any help will be appreciated!
EDIT:
View Model
public class MyViewModel extends AndroidViewModel {
private Dao dao;
private ExecutorService executorService;
public MyViewModel (#NonNull Application application) {
super(application);
dao = AppDatabase.getInstance(application).getDao();
executorService = Executors.newSingleThreadExecutor();
}
public LiveData<Data> getData() {
return dao.getAllData();
}
// other methods to update and delete with executorService
}
I managed to get the result you want by seting a common lifecycle owner for the viewmodel, and using the same viewModel at the different fragments.
Inside my fragment, i get the view model like this:
INSIDE FRAGMENT:
var userViewModel = activity?.run{ViewModelProviders.of(this, SharedUserViewModel.MainActivityViewModelFactory(applicationContext))[SharedUserViewModel::class.java]}
Did you see i use the activity as the "ViewModelProviders.of" parameter?
This way, the view model have the same owner, its working alright for me.
Yes you can share ViewMode between activity and Fragment Using SharedViewModel.
I have MainActivity which contains TabLayout with tabs: each tab is a fragment and each one has a RecyclerView. When I tap FAB in Main Activity, NewReminderActivity is opened.
I use Architecture Components: Entity(Reminder), DAO, Room, ViewModel, LiveData and Repository.
The question is:
Which methods should I use to deliver a new created reminder item into the fragment (which contains as mentioned above a RecyclerView?
I have some ideas, but could you help me please and give me a right direction for implementing:
1) I guess, I should deliver data to MainActivity, then from MainActivity to the fragment and use ViewModel as mentioned in https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing , am I right?
2) I guess I should use setResult() in NewReminderActivity, am I right?
If you are using Room, there is no reason for you to use setResult to transfer new item to any of these previous Fragments/Activities, because Room manages invalidation automatically.
#Dao
public interface MyDao {
#Query("SELECT * FROM ITEM")
LiveData<List<Item>> getItemsWithChanges();
#Insert(onConflict = OnConflictStrategy.REPLACE)
void insertItem(Item item);
}
Then
public class MyViewModel extends ViewModel {
private final LiveData<List<Item>> items;
public LiveData<List<Item>> getItems() {
return items;
}
public MyViewModel(MyDao myDao) {
items = myDao.getItemsWithChanges();
}
}
Then
public class MyFragment extends Fragment {
MyViewModel myViewModel;
#Override
protected void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
myViewModel = ViewModelProviders.of(getActivity(), viewModelFactory).get(MyViewModel.class);
myViewModel.getItems().observe(getViewLifecycleOwner(), (items) -> {
if(items != null) {
adapter.submitList(items);
}
});
}
}
In which case all you need to do in your second Activity is insert the new item, then finish out:
// imagine this is on background thread
myDao.insertItem(item);
runOnUiThread(() -> {
finish();
});
And all your RecyclerViews will update with the new item (if they are part of the results as the condition matches it).
In this problem I want to get Data from My API
In which I get Data from Retrofit
I want to show data in RecyclerView in Fragment Tabs but how can I send data from activity to Fragment
This is all I have tried
Retrofit call which provide me ArrayList of my posts
getMainApp().swiftAPI.getPosts().enqueue(object : Callback<ArrayList<Post>>{
override fun onFailure(call: Call<ArrayList<Post>>?, t: Throwable?) {
Toast.makeText(this#DashboardActivity, t?.message, Toast.LENGTH_SHORT)
}
override fun onResponse(call: Call<ArrayList<Post>>?, response: Response<ArrayList<Post>>?) {
if (response?.isSuccessful!!){
}
}
PagesFragment
val rootView = inflater.inflate(R.layout.fragment_page, container, false)
val video_recyclerview = rootView.findViewById(R.id.pages_rcv) as RecyclerView // Add this
video_recyclerview.layoutManager = LinearLayoutManager(activity)
video_recyclerview.adapter = PagesAdapter()
return rootView
I want to Know if there is any way possible to send ArrayList to fragment cause my data is in ArrayList
You can define an interface in your activity and let the fragment implement the interface. You can follow this example on my github: ActivityToFragmentCommunication
Basically, in your activity define:
public interface DataLoadedListener {
public void onDataLoaded(ArrayList<Post> posts);
}
Then, make your fragment implement the interface like below:
public class ExampleFragment extends Fragment implements MainActivity.DataLoadedListener {
// your fragment code
}
Finally in the onCreate() method of your activity:
// Create new fragment and transaction
mExampleFragment = new ExampleFragment();
// setting mExampleFragment as data load listener
mDataLoadedListener = (DataLoadedListener) mExampleFragment;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack if needed
transaction.replace(R.id.flContainer, mExampleFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
// load data after click
btLoadData.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
loadData();
// notify attached fragment
mDataLoadedListener.onDataLoaded(myStrings);
}
});
First check currentFragment in your activity class. You will get the fragment reference object in your activity class . so the example will like below:
Suppose you have a fragment called DataFragment and you have a reference mDataFragment in your activity class. now when you get data in your activity class you will call ((DataFragment)mDataFragment).passData(yourDataList). Remember passData() is a public method in your fragment class. Then you can add data in adapter and call notifyDataSetChanged()
Since you are working on Android, I would recommend the ViewModel component which makes it really easy to communicate between a activity and it's fragments.
First add the package to your app
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
Then create a ViewModel class
public class MyViewModel extends ViewModel {
public MutableLiveData<ArrayList<Post>> posts = new MutableLiveData<ArrayList<Post>>();
}
Now in the fragment subscribe to it.
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.posts.observe(this, { posts ->
// Update the UI.
});
}
}
Then set the value in your MainActivity as shown below and voila you have the data in your fragment. You can read more about it here
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.users.setValue(posts);
}
}
I'm trying to create a ViewModel in MainActivity, which observes to some data changes in some singleton component. The goal is to use that ViewModel in a few fragments of this activity. But so far even without involving the fragment yet it doesn't work. The app gets stuck at launch, printing :
java.lang.RuntimeException: Unable to start activity ComponentInfo{my_package.MainActivity}: java.lang.RuntimeException: Cannot create an instance of class my_package.MyViewModel
my_package.App cannot be cast to android.arch.lifecycle.LifecycleOwner
the problem seems to be in the line : MyCustomSingletonComponent.getInstance().getSomeDataLiveData().observe......
The Code :
public class MyCustomSingletonComponent
{
public MutableLiveData<CustomClass> someData = new MutableLiveData<>();
private static final MyCustomSingletonComponent instance = new MyCustomSingletonComponent();
private MyCustomSingletonComponent() {
someData = new MutableLiveData<>();
}
public static MyCustomSingletonComponent getInstance() {
return instance;
}
public LiveData<CustomClass> getDataLiveData()
{
return someData;
}
}
public class MyViewModel extends AndroidViewModel {
public MyViewModel(#NonNull Application application)
{
super(application);
MyCustomSingletonComponent.getInstance().getSomeDataLiveData().observe(getApplication(), myData -> {
...
});
}
}
public class MainActivity extends AppCompatActivity
{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
....
}
}
First, an Application is not a LifecycleOwner, so you cannot pass it to observe() on a LiveData. Activities and fragments are standard lifecycle owners.
Second, IMHO, a ViewModel should not be observing anything. The thing that uses the ViewModel does the observing. MyViewModel might hold onto the LiveData, but then MainActivity is the one that observes.