16

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.

enter image description here

nik7
  • 806
  • 3
  • 12
  • 20

4 Answers4

34

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'
BVantur
  • 1,182
  • 16
  • 11
  • I appreciate your answer . I could not share data using ViewModel class because of the fragment is not inside of my activity class, so that I created new fragment then replace to the activity class. And It works perfectly. Thank you very much. –  Mar 27 '20 at 17:34
  • 3
    If you are using Navigation component, you can also scope your ViewModels by navigation graph like this: `val myViewModel: TimeTableViewModel by navGraphViewModels(R.id.navigation_graph)`. Glad I could help. :) – BVantur Mar 27 '20 at 18:30
  • what is the benefit of doing so? – nyx69 Aug 09 '20 at 01:16
  • Which part do you mean? Navigation scoped ViewModels? – BVantur Aug 10 '20 at 07:35
  • 1
    With the latest directions from the Android team, we are usually using Single Activity in combination with Navigation component for building our apps. If this is a case, then we are actually creating a singleton that lives through the whole app if we use Activity ViewModel. To avoid that, we could break our app into different navigations and scope our logic components properly by navigation use case. For example, we don't need some OnboardingManager inside the Home screen of a logged-in user. – BVantur Aug 12 '20 at 13:31
  • I know that already. My question ´was wether there is any difference between val myViewModel: TimeTableViewModel by navGraphViewModels(R.id.navigation_graph) and myViewModel = ViewModelProvider.AndroidViewModelFactory(Application()).create(MyViewModel::class.java) – nyx69 Aug 13 '20 at 16:13
8

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()
Umair Mustafa
  • 209
  • 4
  • 13
mhKarami
  • 844
  • 11
  • 16
  • 2
    Is the first line of code misleading? You won't be able to access a ViewModel setup in the Activity using `private val fragmentViewModel: Fragment1ViewModel by viewModels()` – Chucky Apr 08 '22 at 11:46
3

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

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

Orbital
  • 906
  • 1
  • 8
  • 17
Zeus
  • 53
  • 1
  • 5