I am working with Android architecture components.
What i want is when user type "0" in Edittext and click on Button to replace Fragment with new one , and if type anything else post Toast error message. In Problem is when i back from new Fragment(BlankFragment) and click on button again and type "0" again and click, onchange() is called multiple times so Fragment is get created multiple times
FragmentExample.class:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
manager = getActivity().getSupportFragmentManager();
viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(VModel.class);
View v = inflater.inflate(R.layout.fragment_list, container, false);
b = (Button) v.findViewById(R.id.b);
et = (EditText) v.findViewById(R.id.et);
viewmModel.observeData().observe(getActivity(), new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
if(s.equals("0")) {
BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
if (fragment == null) {
fragment = BlankFragment.newInstance();
}
addFragmentToActivity(manager,
fragment,
R.id.root_activity_detail,
DETAIL_FRAG
);
} else {
Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
}
}
});
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
viewmModel.setData(et.getText().toString());
}
});
return v;
}
private void addFragmentToActivity(FragmentManager fragmentManager, BlankFragment fragment, int root_activity_detail, String detailFrag) {
android.support.v4.app.FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(root_activity_detail, fragment, detailFrag).addToBackStack(detailFrag);
transaction.commit();
}
Repository class:
public class Repository {
MutableLiveData<String> dataLive = new MutableLiveData<>();
public Repository() {
}
public void setListData(String data) {
dataLive.setValue(data);
}
public MutableLiveData<String> getData() {
return dataLive;
}
}
BlankFragment.class:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
listItemViewModel = ViewModelProviders.of(this, viewModelFactory)
.get(VModel.class);
listItemViewModel.setData("");
return inflater.inflate(R.layout.fragment_blank, container, false);
}
Here is an example how i solve this problem .[TESTED AND WORKING]
viewModel.getLoginResponse().observe(getViewLifecycleOwner(), new Observer<String>() {
#Override
public void onChanged(String response) {
if(getViewLifecycleOwner().getLifecycle().getCurrentState()== Lifecycle.State.RESUMED){
// your code here ...
}
}
});
The problem here is that when you dettach the fragment from the acitivity, both fragment and its viewmodel are not destroyed. When you come back, you add a new observer to the livedata when the old observer is still there in the same fragment (If you add the observer in onCreateView()).
There is an article (Even a SO thread in fact) talking about it (with solution).
The easy way to fix it (also in the article) is that remove any observer from the livedata before you add observer to it.
Update:
In the support lib v28, a new LifeCycleOwner called ViewLifeCycleOwner should fix that more info in here
here is what you are doing wrong...
viewmModel.observeData().observe(getActivity(), new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
if(s.equals("0")) {
BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
if (fragment == null) {
fragment = BlankFragment.newInstance();
}
addFragmentToActivity(manager,
fragment,
R.id.root_activity_detail,
DETAIL_FRAG
);
} else {
Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
}
}
});
in above code instead of "getActivity()" either you can use "this" or "viewLifecycleOwner".
Because as you are passing the getActivity() in observe method, whenever you open your fragment you are attaching the new instance of the observer with the Activity not with the fragment. So observer will keep alive even if you kill your fragment.
So when livedata postvalue, it will send data to all the observers,
as there are too many observers observing livedata, then all will get notified.
Because of this, your observer gets called too many times.
so you have to observe live data in fragment something like this.
viewmModel.observeData().observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
if(s.equals("0")) {
BlankFragment fragment = (BlankFragment) manager.findFragmentByTag(DETAIL_FRAG);
if (fragment == null) {
fragment = BlankFragment.newInstance();
}
addFragmentToActivity(manager,
fragment,
R.id.root_activity_detail,
DETAIL_FRAG
);
} else {
Toast.makeText(getContext(), "Wrong text", Toast.LENGTH_SHORT).show();
}
}
});
But still your onchanged method will get called two times.
You can stop this by checking one condition inside your onchanged method..
dash_viewModel.getDashLiveData().observe(viewLifecycleOwner, object : Observer<AsyncResponse> {
override fun onChanged(t: AsyncResponse?) {
if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
setData(t)
}
}
})
from my research, I have found out that, if fragment using the ViewModel of its corresponding activity,
So when even you start observing the livedata, it will first send you the most recently emitted item. even if you didn't call it from your fragment.
so onChange method got called two times
When the fragment is on start state - to receive the most recently emitted item
When the fragment is on Resumed state - to receive the call made by fragment either for api.
so on changed I always check the state of the fragment with the help of viewLifecycleOwner like this
if(viewLifecycleOwner.lifecycle.currentState==Lifecycle.State.RESUMED){
// if the fragment in resumed state then only start observing data
}
viewlifecycleowner is provided by both Fragments and Activity as Google implemented this solution directly in support library 28.0.0 and androidx with getViewLifecycleOwner() method. viewlifecycleowner contains info about the lifecycle of the component.
in java you can use getViewLifecycleOwner() intead of viewlifecycleowner .
Instead of using getActivity as LifecycleOwner, you should use fragment.
Change
viewModel.observeData().observe(getActivity(), new Observer<String>() {
to
viewModel.observeData().removeObservers(this);
viewModel.observeData().observe(this, new Observer<String>() {
You shouldn't create your viewmModel in onCreateView but rather in onCreate so you don't add a listener to your data each time view is created.
Solution for similar issue with Flow
If you`re using Flow instead of LiveData then don't forget to use viewLifecycleOwner.lifecycleScope.launch instead of lifecycleScope.launch:
viewLifecycleOwner.lifecycleScope.launch {
flow.flowOn(Default).collect {
requireContext()
}
}
Or with extension:
extension:
fun <T> Flow<T>.launchWhenStarted(lifecycleOwner: LifecycleOwner) {
lifecycleOwner.lifecycleScope.launchWhenStarted {
this#launchWhenStarted.collect()
}
}
in fragment`s onViewCreated:
availableLanguagesFlow
.onEach {
//update view
}.launchWhenStarted(viewLifecycleOwner)
to add an observer to a LiveData, you should initialize your observer in onViewCreated (as you did) and attach the viewLifecycleOwner. So, it should look like this:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.yourVariable.observe(viewLifecycleOwner, Observer{ newValue ->
// todo your function
})
See Kotlin Android Training Livedata for more information!
Just declare your Observer as a field variable so you don't create a new observer every time the lifecycle calls that part of your code. ;)
i.e. with kotlin:
YourFragment: Fragment() {
private val dataObserver = Observer<Data> { data ->
manageData(data)
}
...
//now you should subscribe your data after you instantiate your viewModel either in onCreate, onCreateView, onViewCreated, depends on your case..
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.liveData.observe(this, dataObserver)
}
...
}
To be clear, to instantiate a ViewModel we pass either the context of the Fragment itself, in which this viewmodel will be scoped to, or we pass the activity that holds this fragment as the context.
Is not the same and they have different purposes, if we do this
listItemViewModel = ViewModelProvider(requireActivity(), viewModelFactory)
.get(VModel.class);
We attach this instance of the viewmodel to the parent activity of the fragment, after this fragment dies , the instance of that viewmodel will be kept in memory holding the reference to the parent activity.
If we do this
// requireContext() or this
listItemViewModel = ViewModelProvider(requireContext(), viewModelFactory)
.get(VModel.class);
We scope the instance of the viewmodel in the parent itself, so whenever the fragment dies, this viewmodel instance also gets removed.
Now, the observers are the same, we need to specify that we only want to observer to the lifetime span of the Fragment, for example, we want to observer untill this fragment is destroyed and then detach any observer if we are not in this fragment, for this, here it comes viewLyfeCycleOwner which will observe until this fragment dies or pauses to go to antoher fragment, it's important to use it in each fragment:
viewmModel.observeData().observe(viewLyfeCycleOwner, new Observer<String>() { ... }
if we attach this observer to the activity with
viewmModel.observeData().observe(getActivity(), new Observer<String>() { ... }
It will keep observing until the parent activity holding the fragment dies, and is not a good idea since it will subscribe multiple observers to the same livedata.
viewmModel = ViewModelProviders.of(getActivity(), viewModelFactory)
.get(VModel.class);
As your viewmModel's LifecycleOwner is activity, so the observer will only be automatically removed when the state of lifecycle is Lifecycle.State.DESTROYED.
In your situation, the observer will not be automatically removed.So you have to remove the previous observer manually or pass the same instance of observer every time.
I had the same problem, when I created a fragment and declared an observer into onCreate() or onCreateView(), after a rotation of screen, my livedata runned twice times.
In order to solve this problem, I tried kotlin extension in order to remove observer just before to create an other one, I tried to remove into onDestroyView(), I tried to change the lifecyclcleOwner when the viewmodel declaration (requiredActivity - this - viewLifecycleOwner) but all tests failed.
But finally I found a solution with coroutines. I don't know if it's a good practice but it work :
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// ......
// observer
CoroutineScope(Dispatchers.Main).launch {
delay(100)
mMainViewModel.getMailings().observe(viewLifecycleOwner, Observer { showViewModelMailingsList(it) })
}
// call
mMainViewModel.responseMailingList(preferences.userEmailAccount, preferences.deviceToken)
// ....
}
Adding some useful information after #Samuel-Eminet,
it's true that onCreate(Bundle?) is called only once on the Fragment creation and when you press back, the view is recreated but not the fragment (hence why the ViewModel is the same. If you subscribe in any method of the lifecycle that affects the view, it will resubscribe again and again.
Observers would have been gone and you won't be able to tell even if you ask for liveData.hasObservers().
The best thing to do is subscribing when onCreate(Bundle?) but many of us are using the binding, and the view isn't created at this time, so this is the best way to do it:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launchWhenStarted {
subscribeUI()
}
}
now you're telling the Fragment's lifecycle to do something when it starts the Fragment and it will call it only once.
Observe the livedata only once in a fragment. For that call the observe method in onCreate() rather than onCreateView(). When we press back button the onCreateView() method is called which makes the viewmodel to observe data again.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPatientViewModel.getGetCaseDetailLiveData().observe(this, jsonObjectResponse -> parseViewSentResponse(jsonObjectResponse));
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// TODO: inflate a fragment view
View rootView = super.onCreateView(inflater, container, savedInstanceState);
return rootView;
}
Related
Let's assume a fragment has this ActivityResultLauncher:
class MyFragment : Fragment(R.layout.my_fragment_layout) {
companion object {
private const val EXTRA_ID = "ExtraId"
fun newInstance(id: String) = MyFragment().apply {
arguments = putString(EXTRA_ID, id)
}
}
private val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
Timber.i("Callback successful")
}
}
...
This Fragment a wrapped in an Activity for temporary architectural reasons, it will eventually be moved into an existing coordinator pattern.
class FragmentWrapperActivity : AppCompatActivity() {
private lateinit var fragment: MyFragment
private lateinit var binding: ActivityFragmentWrapperBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityFragmentWrapperBinding.inflate(this)
setContentView(binding.root)
fragment = MyFragment.newInstance("blah")
supportFragmentManager.transact {
replace(R.id.fragment_container, fragment)
}
}
}
And we use that launcher to start an Activity, expecting a result:
fun launchMe() {
val intent = Intent(requireContext(), MyResultActivity::class.java)
launcher.launch(intent)
}
On a normal device with plenty of available memory, this works fine. MyResultActivity finishes with RESULT_OK, the callback is called and I see the log line.
However, where memory is an issue and the calling fragment is destroyed, the launcher (and its callback) is destroyed along with it. Therefore when MyResultActivity finishes, a new instance of my fragment is created which is completely unaware of what's just happened. This can be reproduced by destroying activities as soon as they no longer have focus (System -> Developer options -> Don't keep activities).
My question is, if my fragment is reliant on the status of a launched activity in order to process some information, if that fragment is destroyed then how will the new instance of this fragment know where to pick up where the old fragment left off?
Your minimal fragment is unconditionally replacing the existing fragment with a brand new fragment everytime it is created, thus causing the previous fragment, which has had its state restored, to be removed.
As per the Create a Fragment guide, you always need to wrap your code to create a fragment in onCreate in a check for if (savedInstanceState == null):
In the previous example, note that the fragment transaction is only created when savedInstanceState is null. This is to ensure that the fragment is added only once, when the activity is first created. When a configuration change occurs and the activity is recreated, savedInstanceState is no longer null, and the fragment does not need to be added a second time, as the fragment is automatically restored from the savedInstanceState.
So your code should actually look like:
fragment = if (savedInstanceState == null) {
// Create a new Fragment and add it to
// the FragmentManager
MyFragment.newInstance("blah").also { newFragment ->
supportFragmentManager.transact {
replace(R.id.fragment_container, newFragment)
}
}
} else {
// The fragment already exists, so
// get it from the FragmentManager
supportFragmentManager.findFragmentById(R.id.fragment_container) as MyFragment
}
So, I would like to use StateFlow instead of LiveData, but I can not figure out what's the problem with my logic.
I have a flow, which has a default null value. When I open a dialog which contains a some datas, after that I select one data, I emit the new value to the flow.
In the first time, after the dialog closed, collectLatest called, and I get the null value (init), after the emit, I get the new value, it is good. But If I open the dialog again, and select value, and close the dialog, the collectLatest fun called 3-times, and I again open the dialog... and collectLatest called 4 times and so on.
So this is very bad behavior, and I'm sure , I did something wrong, but I don't see the bug.
In the liveData the expected behavior is after the dialog close, that the observer fun is called just once. I would like to achive this.
I also checked, that I emit the new value only once, so there is no reason why collectLatest fire multiple times.
ViewModel:
private val _previousManufacture = MutableStateFlow<PreviousManufactureView?>(null)
val previousManufacture = _previousManufacture.asStateFlow()
private suspend fun setPreviousManufactureByMachineId(machineId: String) {
val result = stateReportRepository.getPreviousManufactureByMachineId(machineId)
if (result is Result.Success) {
_previousManufacture.emit(result.data)
} else {
_previousManufacture.emit(null)
}
}
Fragment:
lifecycleScope.launchWhenCreated {
viewModel.previousManufacture.collectLatest {
var d = it
}
}
[Update]
Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.vm = viewModel
initFlows()
}
private fun initFlows() {
lifecycleScope.launchWhenCreated {
viewModel.openStateOfWorkflowBrowser.collectLatest {
openStateOfWorkflowSelectionDialog()
}
}
...
}
Sorry, I missed this before in my comment, but I think the problem is that you are calling launchWhenCreated in the lifecycleScope of the Fragment, not in its viewLifecycle.lifecycleScope. So if the Fragment is reused (like after a dialog fragment has a appeared), the old collector is not cancelled and a new one is added, because the lifecycle of the Fragment has not ended, only the lifecycle of its previous view. You should almost always use viewLifecycle.lifecycleScope when you are using coroutines in a Fragment.
I am working with RecyclerView and using Retrofit to fetch the data from Server. I am using Kotlin with MVVM Design Pattern. I have used LiveData it was working fine. But with Stateflow causing issues when we navigate to another Fragment and Comes back to the Same Fragment again. It just fetches the same data again. Below is the code for ViewModel and the observer:
//View Model
private val _allTimeSheetsResponse =
MutableStateFlow<ResponsesResult<AllTimeSheetsResponse>>(ResponsesResult.Empty)
val allTimeSheetsResponse : StateFlow<ResponsesResult<AllTimeSheetsResponse>> get() = _allTimeSheetsResponse
fun getAllTimeSheets(auth: String) =
viewModelScope.launch {
timeSheetsRepository.getAllTimeSheets(auth).collect {
_allTimeSheetsResponse.value = it
}
}
//Observer
lifecycleScope.launchWhenStarted{
timeSheetsViewModel.allTimeSheetsResponse.collect { timeSheetsResponse ->
when (timeSheetsResponse) {
is ResponsesResult.Loading -> binding.progressBarLayout.show()
is ResponsesResult.Failure -> {
binding.progressBarLayout.gone()
binding.nothingFoundLayout.show()
handleApiError(timeSheetsResponse)
}
is ResponsesResult.Success -> {
binding.progressBarLayout.gone()
if (timeSheetsResponse.value.payload.isNotEmpty()) {
showAllTimeSheetsRecyclerAdapter.submitList(timeSheetsResponse.value.payload)
} else {
binding.nothingFoundLayout.show()
}
}
else -> Unit
}
}
}
Because you call getAllTimeSheets many times (eg. onCreateView or onViewCreated). Trying call it when accessing allTimeSheetsResponse` for the first time.
Your ViewModel's getAllTimeSheets() function starts a new coroutine to collect from the repo's cold Flow each time you call it, so each time the Fragment comes back, presumably. You should remove this function and simply convert the repo's cold Flow directly to a StateFlow:
val allTimeSheetsResponse: StateFlow<ResponsesResult<AllTimeSheetsResponse>> =
timeSheetsRepository.getAllTimeSheets(auth)
.stateIn(viewModelScope, SharingStarted.Eagerly, ResponsesResult.Empty)
You can pass the auth parameter into the ViewModel's factory through to its constructor.
When you are using the navigationComponent and call navController.navigate() to open a fragment, in the background destination fragment replaces with the old destination's fragment. so old fragment will keep in the fragmentManager backStack. but its view will destroy. and when navigate back, old fragment comes from backStack (not created again) and just its view creates.
So it's better to call getAllTimeSheets() in Fragment's onCreate. (to call one time). When fetching done, all data will set in _allTimeSheetsResponse
And then you should observe allTimeSheetsResponse in onViewCreated with viewLifecycleOwner scope.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.allTimeSheetsResponse.onEach { response ->
// do sth with response
}.launchIn(viewLifecycleOwner.lifecycleScope)
}
fun getAllTimeSheets(auth: String) :StateFlow<ResponsesResult<AllTimeSheetsResponse>> {
var mutableStateFlow = MutableStateFlow<ResponsesResult<AllTimeSheetsResponse>>(ResponsesResult.Empty)
viewModelScope.launch {
timeSheetsRepository.getAllTimeSheets(auth).collect {
mutableStateFlow.value = it
}
}
return mutableStateFlow
}
The viewmodel observe is triggering up many times(3-4) in a row for the same event in both fragment and activity. Here's the viewmodel code:
userModel.getResponseSuperRegion(countryID)
userModel.responseSuperRegion.observe(this,
Observer {
if(it!=null) {
Log.e(TAG,"Observer ran!!!!")
Utils.debugger("FRAG ", "$it")
requestResults(countryID, date, spec, it!!.get(0).nameValue, businessUnit)
}else{
Toast.makeText(applicationContext, "Data Not Available!", Toast.LENGTH_LONG).show()
dismissProgress()
}
})
the same type of code i am using for fragment and there too I am receiving the same issue. I have tried by using viewLifecycleOwner but that is also not working out in case of fragments and viewLifecycleOwner is not there for activity so please suggest me some way to get rid of this issue. I have read majority of the similar SO threads but none of them is working.
Update: already tried by
userModel.responseSuperRegion.removeObservers(this)
This is working like charm, basically remove the subscribers once the trigger has generated.
userModel.getResponseGrowthSpinner(businessUnitID, isGroup, firstReportTypeId)
userModel.responseGrowthSpinner.observe(this,
Observer {
Utils.debugger("FRAG ", "$it")
purchase_options_spinner.adapter = GrowthSpinnerAdapter(it)
userModel.responseGrowthSpinner.removeObservers(this) //add this line
})
are u calling view model at oncreate()? like
#Override
protected void onCreate(Bundle savedInstanceState) {
...
//init ViewModel
UserViewModel userModel = ViewModelProviders.of(this)
.get(UserViewModel.class);
userModel.getResponseSuperRegion(countryID);
}
Edit :
if you use kotlin change your view model to init function
change on viewModel
class UserModel(val countryID:String):ViewModel(){
init{
getResponseSuperRegion(countryID)
}
fun getResponseSuperRegion(countryID){
...
}
}
then in activity or baseactivity remove this
userModel.getResponseSuperRegion(countryID)
then change
val viewModel = ViewModelProviders.of
(this,CustomViewModelFactory("countryID")).get(UserViewmodel::class.java)
to passing parameter countryID from activity to viewmodel
I have a fragment A which sends a search query to the network, and if the result is positive uses Android navigation component to navigate to fragment B, and its done using observers.
After navigation to fragment B, i click on "<-" arrow on the top of the screen, but instead of navigating back to fragment A it reloads fragment B again. And if using the native "back" button on the device, the app crashes with "illegalArgumentException navigation destination unknown" error.
I check the internet for clues on this issue, but all i learned is that this happens because i am using .observe in onViewCreated() and when i go back, it gets called again, and because livedata has something in it already, it just navigates me back to B.
I have tried observing in onActivityCreated(), and using getViewLifeCycleOwner, but no success... the only thing that helped is checking if livedata has observers and returning if true, before using .observe, but it seems incorrect.
This is the viewModel:
private val getAssetResult = MutableLiveData<GeneralResponse<Asset>>()
private val updateAssetResult = MutableLiveData<GeneralResponse<Int>>()
private val deleteAssetResult = MutableLiveData<GeneralResponse<Int>>()
init {
state.value = ViewState(false)
Log.d(TAG, "State in init: $state")
}
fun getAssetResult(): LiveData<GeneralResponse<Asset>>{
return getAssetResult
}
fun findAsset(req: GetAssetRequest) {
scope.launch {
setProgressIndicator(true)
val result = repository.getAsset(req)
getAssetResult.postValue(result)
setProgressIndicator(false)
}
}
This is the fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(EditAssetViewModel::class.java)
setupViewModel()
initFields()
}
private fun setupViewModel() {
if (viewModel.getAssetResult().hasObservers()) // <- This is the part that prevents the app from crashing.
return
viewModel.getAssetResult().observe(this, Observer {
if (it == null) return#Observer
handleSearchResult(it)
})
if (viewModel.getState().hasObservers())
return
viewModel.getState().observe(this, Observer { handleState(it) })
}
private fun handleSearchResult(response: GeneralResponse<Asset>) {
if (response.singleValue == null) {
Toast.makeText(context!!, response.errorMessage, Toast.LENGTH_SHORT).show()
return
}
targetFragment?.let { it ->
val bundle = bundleOf("asset" to response.singleValue)
when(it) {
"UpdateLocation" ->
Navigation.findNavController(view!!).navigate(R.id.updateLocation, bundle)
"EditAsset" -> {
Navigation.findNavController(view!!).navigate(R.id.editAsset, bundle)
}
}
}
}
if i remove this part from the setupViewModel function:
if (viewModel.getAssetResult().hasObservers())
return
the app will either crash when clicked "back" using the device button or go back to fragment A, just to be navigated back to fragment B because of the .observe function.
Override the method onBackPressed() to handle the "<-" arrow
Seems like the LiveData that you use to signal to fragment A that it should navigate to fragment B is actually an event. An event happens only once and once it is consumed (navigation event is done), it is gone. Therefore, after navigating you need to send a message to the viewmodel that the navigation took place and that the corresponding data holder should be (e.g.) null again. In Fragment A you check that the new value is unequal to null, and only if this is the case, you issue the navigation event. This would prevent fragment A to immediatelly switch to B again in the back scenario.
If you want to learn more about ways to use live data for events, please refer to this article.