I have an Activity that kicks off three fragments. The first is a loading splash screen where I just want to show the name of the app for a couple seconds. The second, in my current case, is a screen that gathers the users name and language. The third fragment will gather which languages they would like to learn. I'm trying to use an Observable in my ViewModel to hold my data, but the info entered at the first fragment is being lost upon switching to the second fragment.
I think I'm running into two problems (or more) simultaneously:
- I don't think I'm sharing my runOnceViewModel correctly across fragments
- I don't think I'm using Observables quite right.
After permissions are checked from the splash screen, I fire a function (launchProfile) that loads the next fragment like this:
public void launchProfile(){
if(model.getLearnerPK() > 0){
learningScreenFragment fragment = new learningScreenFragment(model.getUser());
getParentFragmentManager()
.beginTransaction()
.addToBackStack(null)
.replace(R.id.fragment_container, fragment, null)
.commit();
} else {
runOnceNameAndNativeLangFragment fragment = new runOnceNameAndNativeLangFragment();
getParentFragmentManager()
.beginTransaction()
.addToBackStack(null)
.replace(R.id.fragment_container, fragment, null)
.commit();
}
}
Since this is a new user, the primary key will be 0 and so I load the runOnceNameAndNativeLangFragment:
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Log.d(LOG_TAG, "RUNONCE: Gather Name and Native Language");
mBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.runonce_name_native, container, false);
TransitionInflater TI = TransitionInflater.from(requireContext());
setEnterTransition(TI.inflateTransition(R.transition.slide_right));
return mBinding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState){
runOnceViewModelFactory factory = new runOnceViewModelFactory(requireActivity().getApplication());
model = new ViewModelProvider(this,factory).get(runOnceViewModel.class);
//super.onViewCreated(view,savedInstanceState); //remains of a road I tried to go down, but perhaps should have gone farther...
//model = new ViewModelProvider(requireActivity()).get(runOnceViewModel.class);
ArrayList<language> spinnerListLangs = model.getLangList();
nativeSLAdapter = new SpinLangAdapter(m_Context, android.R.layout.simple_spinner_dropdown_item, spinnerListLangs);
mBinding.spCreateNativeLang.setAdapter(nativeSLAdapter);
mBinding.spCreateNativeLang.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> adapterView, View view, int position, long l) {
language lang = nativeSLAdapter.getItem(position);
tmpNativeLangNo = lang.getLangNo();
}
@Override
public void onNothingSelected(AdapterView<?> adapterView) {}
});
mBinding.btnNext.setOnClickListener(this::onClick);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.btnNext) {
String tmpName = mBinding.etCreateProfileName.getText().toString();
if(tmpNativeLangNo != null && !tmpName.equals("")){
model.getNewUser().observeOn(Schedulers.io()) //I'm doing something dumb here
.map(user -> {
user.setName(tmpName);
user.setNativeLang(tmpNativeLangNo);
return user;
});
runOnceSelectTargetLangsFragment fragment = new runOnceSelectTargetLangsFragment();
getParentFragmentManager()
.beginTransaction()
.addToBackStack(null)
.replace(R.id.fragment_container, fragment, null)
.commit();
} else {
Toast.makeText(m_Context,"Name and Native Language required.",Toast.LENGTH_SHORT).show();
}
}
}
If I put a breakpoint on "runOnceSelectTargetLangsFragment fragment = new runOnceSelectTargetLangsFragment();" then I can verify that model.getNewUser() returns an Observable with the name and native language of whatever I input on the UI. What's killing me is that once I load the next fragment (runOnceSelectTargetLangsFragment), and try to save the target languages in its click handler, the name and native language info are lost. Ala, I'm not sharing the viewmodel right (or, worse, I'm using an Observable when I shouldn't be -- side note: I chose to use an Observable because it makes handling the thread stuff quite a bit easier and I imagine I'll appreciate its flexibility down the road as the app becomes more complicated. At least, that's what I'm telling myself.)
Select Targets Fragment
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
Log.d(LOG_TAG, "RUNONCE: Gather Target Languages");
mBinding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.runonce_select_targets, container, false);
TransitionInflater TI = TransitionInflater.from(requireContext());
setEnterTransition(TI.inflateTransition(R.transition.slide_right));
return mBinding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState){
//runOnceViewModelFactory factory = new runOnceViewModelFactory(requireActivity().getApplication());
//model = new ViewModelProvider(this,factory).get(runOnceViewModel.class); //I think this was on the right track...maybe?
//super.onViewCreated(view,savedInstanceState);
model = new ViewModelProvider(requireActivity()).get(runOnceViewModel.class);
mBinding.btnNext.setOnClickListener(this::onClick);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.btnNext) {
if(hasChecked()){
model.getNewUser().observeOn(Schedulers.io())
.subscribe(user -> {
user.setTargetLangs(getTargetLangs());
});
String restHereWearyTraveler = ""; //the inn :)
//...do more stuff
} else {
Toast.makeText(m_Context,"You must select at least one target language.",Toast.LENGTH_SHORT).show();
}
}
}
At the inn, model.getNewUser() returns an Observable with the target languages all set, but the name and native language info are gonezo.
View Model
public class runOnceViewModel extends AndroidViewModel {
private final profileRepository m_Repository;
private profileService PCS;
private ArrayList<language> m_Langs;
private Observable<User> newUser;
public runOnceViewModel(Application application, profileRepository newProfileRepo) {
super(application);
m_Repository = newProfileRepo;
m_Langs = m_Repository.getLanguages();
PCS = profileService.getInstance(m_Repository);
}
public Observable<User> getNewUser(){
if(newUser == null){
User tmpUser = new User();
newUser = Observable.just(tmpUser);
}
return newUser;
}
}