How to remove an observer from livedata so it doesn't show twice when navigating back to the fragment
Asked Answered
B

3

7

I have a fragment which displays a popup when the user is successfully logged in. If I navigate to a new fragment and come back, the popup with the previous username is shown again. I fixed this problem using SingleLiveEvent, but I now have to refactor my code to use MediatorLiveData as my data can come from 2 sources (remote and database), and it is not compatible with SingleLiveEvent.

I tried using an event wrapper and removing observers on onDestroyView() but so far nothing is working, the livedata onChanged function keeps getting called when I move back to the fragment. Here is some of my fragment:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    binding = FragmentDashboardBinding.inflate(inflater, container, false);
    binding.setLifecycleOwner(getActivity());

    //Get the attendanceViewModel for registering attendance
    attendanceViewModel = ViewModelProviders.of(this).get(AttendanceViewModel.class);
    attendanceViewModel.getAttendance().observe(getViewLifecycleOwner(), attendanceAndMember -> {
        if (attendanceAndMember != null && attendanceAndMember instanceof AttendanceMemberModel) {
            clokedInOutMember = attendanceAndMember.member;
        }
        showResultClockInOutPopup();
    });

    return binding.getRoot();
}

private void showResultClockInOutPopup() {

    clockInBuilder = new AlertDialog.Builder(getActivity());

    View view = getLayoutInflater().inflate(R.layout.status_clock_in_out_popup, null);
    TextView responseClockInOut = view.findViewById(R.id.responseClockInOut);
    Button dismissButton = view.findViewById(R.id.dismissButton);

    //Setup Popup Text
    if (clokedInOutMember != null) {
        if (StringToBool(clokedInOutMember.is_clocked_in_temp)) {
            responseClockInOut.setText("Bienvenue " + clokedInOutMember.name + ", tu es bien enregistré(e).");
        } else {
            responseClockInOut.setText("Désolé de te voir partir " + clokedInOutMember.name + ", à bientôt!");
        }
    } else {
        responseClockInOut.setText("Oups, il semblerait qu'il y ait une erreur...\n Essaye à nouveau.");
    }

    [..SETUP ALERTDIALOG...]

        //Dismiss popup
        dismissButton.setOnClickListener(v -> {
            clockInResultDialog.dismiss();
            clockInResultPopupShowed = false;
            clokedInOutMember = null;
        });

        clockInResultDialog.show();
        clockInResultPopupShowed = true;
    }

}

@Override
public void onDestroyView() {
    attendanceViewModel.getAttendance().removeObservers(this);
    super.onDestroyView();
}

And here is my ViewModel, I have to use transformations as I am getting the userId from the fragment, passing to the Viewmodel which passes it to the repository for query (maybe there is a better way?):

public class AttendanceViewModel extends AndroidViewModel {

private AttendanceRepository repository = AttendanceRepository.getInstance();
public LiveData<AttendanceMemberModel> mAttendanceAndMember;
private MutableLiveData<String> mId =  new MutableLiveData<>();

private MediatorLiveData<AttendanceMemberModel> mObservableAttendance = new MediatorLiveData<AttendanceMemberModel>();
{
    mObservableAttendance.setValue(null);

    mAttendanceAndMember = Transformations.switchMap(mId, id -> {
        return repository.saveAttendance(id);
    });

    mObservableAttendance.addSource(mAttendanceAndMember, mObservableAttendance::setValue);
}


public AttendanceViewModel(@NonNull Application application) {
    super(application);
}

public LiveData<AttendanceMemberModel> getAttendance() {
    return mObservableAttendance;

}

public void setMemberId(String id) {
    mId.setValue(id);
}


@Override
protected void onCleared() {
    mObservableAttendance.setValue(null);
    super.onCleared();
}
}
Belter answered 12/3, 2020 at 4:26 Comment(2)
Your old Observer is being correctly removed, that isn't your problem. LiveData is for state, not for events, so your new Observer is just getting the last state when it starts observing. That's how LiveData works.Censurable
So setting the livedata to null onDestroyView would be the way to solve the issue?Belter
E
2

I can suggest you two ways. First create a boolean variable whether dialog is shown in Fragment and after showing dialog set it to true and before showing dialog check if dialog is shown. Second way is after showing dialog set livedata value to null and check if observer value null before showing dialog. I prefer second way.

Endogenous answered 12/3, 2020 at 5:7 Comment(2)
I tried the second way (setting livedata to null and adding a livedata boolean isReset that I observe before showing the dialog, I need it because I show another popup if livedata is null) and it's working, thank you! I don't know if there is a better way to do this, but if not I will set it as the accepted answer.Belter
I gave you logic and implementation upon you. I can't suggest better way right now.Endogenous
G
1

Use anyone of them, which works and behaves according to your need.

@Override
public void onPause() {
    attendanceViewModel.getAttendance().removeObservers(this);
    super.onPause();
}

@Override
public void onStop() {
    attendanceViewModel.getAttendance().removeObservers(this);
    super.onStop();
}

Fragment Life Cycle

Have a look at the lifecycle of the fragment, it will give you bit more idea. Let me know if this works or not.

Greenbrier answered 12/3, 2020 at 4:50 Comment(2)
I tried that, moving the removeObservers() through all the chain until I put in in onDestroyView, none of them are workingBelter
same here.. it's not working for me as well.Silda
C
0

The best way to the same is to bind your view model in OnViewCreated meathod.

    @Override
    public void onActivityCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        attendanceViewModel = ViewModelProviders.of(this).get(AttendanceViewModel.class);
        setUpObservers();
      }



    private void setUpObservers() {
        attendanceViewModel.getAttendance().observe(getViewLifecycleOwner(), attendanceAndMember -> {
        if (attendanceAndMember != null && attendanceAndMember instanceof AttendanceMemberModel) {
            clokedInOutMember = attendanceAndMember.member;
        }
        showResultClockInOutPopup();
    });
    }

If still it don't work kindly let me know. Thank you.

Corsica answered 12/3, 2020 at 4:35 Comment(5)
Try onActivityCreated insted of onViewCreated!Corsica
@Endogenous In your android studio, create a new Fragment with viewModel and all your doubts may get cleared over there it self.Corsica
@Belter you might consider using CompositeDisposable into your view model to dispose the observer.Corsica
The problem is you start observing on onViewCreated or onActivityCreated and when return that fragment onViewCreated or onActivityCreated called again. She is removing observers onDestroyView but when returning to that fragment, starts observing again and gets last state of viewmodel. That's why your code doesn't work.Endogenous
@Belter Comment all your code in onDestroyView, as LiveData is already lifeCycle aware and please post your whole view model class.Corsica

© 2022 - 2024 — McMap. All rights reserved.