'Going back to previous fragment by back gesture, clears view data

In my application I have 2 main screens "Picture of the day" with picture and some info about it, and the second one "My Favourite" consist of recycler view with favourite pictures. I'm using Navigation Component.

I have problem with displaying data of "Picture Of the day" when coming from "My Favourite". In my application we can make that transition in two ways. One of them is clicking on Bottom Navigation View. When I'm going between this fragments like that, everything is all right - the infomations in "POTD" is shown correctly. Example bellow:

enter image description here .. enter image description here

But... when I want to do the same action, by "back gesture" - swipe finger from right corner of the screen to the center, the information in "POTD" is cleared (example bellow). Why?

enter image description here

PictureOfTheDay Fragment:

@AndroidEntryPoint
class PictureOfTheDay : Fragment() {

    private var _binding: FragmentPictureOfTheDayBinding? = null
    private val binding
        get() = _binding!!

    @Inject
    lateinit var hasNetworkConnection: NetworkConnection
    private val viewModel: PictureOfTheDayViewModel by activityViewModels()

    private var isPictureSet = false

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentPictureOfTheDayBinding.inflate(layoutInflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        activity?.window?.statusBarColor = activity?.let { ContextCompat.getColor(it, R.color.blue_700) }!!
        observeViewModel()
        observeNetworkConnection()
        initViews()
    }

    private fun observeNetworkConnection() {
        hasNetworkConnection.observe(viewLifecycleOwner) {
            viewModel.changeNetworkConnectionStatus(it)
        }
    }

    private fun observeViewModel() {
        viewModel.viewState.observe(viewLifecycleOwner, ::bindState)
        viewModel.viewEvent.observe(viewLifecycleOwner, ::handleEvents)
        viewModel.isFavouriteState.observe(viewLifecycleOwner, ::bindIsFavouriteState)
    }

    private fun bindIsFavouriteState(isAlreadyFavourite: Boolean) {
        if (isAlreadyFavourite) {
            binding.addToFavouriteButton.setColorFilter(
                ActivityCompat.getColor(
                    [email protected](),
                    R.color.red_500
                )
            )
        } else {
            binding.addToFavouriteButton.setColorFilter(
                ActivityCompat.getColor(
                    [email protected](),
                    androidx.appcompat.R.color.material_grey_600
                )
            )
        }
    }

    private fun initViews() {
        binding.addToFavouriteButton.setOnClickListener { viewModel.onFavouriteButtonClick(getBitmap()) }
    }

    private fun getBitmap(): Bitmap {
        val bitmapDrawable = binding.imageOutput.drawable as BitmapDrawable
        return bitmapDrawable.bitmap
    }

    private fun bindState(state: PictureOfTheDayViewState) {
        with(state) {
            if (pictureOfTheDay != null) {
                if (!isPictureSet) {
                    setPictureInfo(pictureOfTheDay)
                    isPictureSet = true
                }
            } else {
                binding.imageCardView.visibility = View.GONE
                binding.infoCardView.visibility = View.GONE
            }

            if (hasInternetConnection) {
                binding.noInternetLayout.visibility = View.GONE
            } else if (!hasInternetConnection) {
                binding.noInternetLayout.visibility = View.VISIBLE
            }

            if (isLoading) {
                binding.loadingProgressBar.visibility = View.VISIBLE
            } else {
                binding.loadingProgressBar.visibility = View.GONE
            }

            if (timeToNewPicture.isNotEmpty()) {
                binding.newPictureInLabel.text = getString(R.string.apod_new_picture_in_time, timeToNewPicture)
                binding.newPictureInLabel.visibility = View.VISIBLE
            } else {
                binding.newPictureInLabel.visibility = View.GONE

            }
        }
    }

    private fun setPictureInfo(pictureOfTheDay: PictureOfTheDayModel) {
        Glide.with(this@PictureOfTheDay)
            .load(pictureOfTheDay.url)
            .into(binding.imageOutput)

        binding.titleOutput.text = pictureOfTheDay.title
        if (pictureOfTheDay.copyright != null) {
            binding.copyrightOutput.text = pictureOfTheDay.copyright
        } else {
            binding.copyrightLabel.visibility = View.GONE
            binding.copyrightOutput.visibility = View.GONE
        }
        binding.dateOutput.text = pictureOfTheDay.date
        binding.explanationOutput.text = pictureOfTheDay.explanation
        binding.imageCardView.visibility = View.VISIBLE
        binding.infoCardView.visibility = View.VISIBLE
    }

    private fun handleEvents(event: PictureOfTheDayViewEvent) {
        when (event) {
            is PictureOfTheDayViewEvent.ShowError -> onShowError(event.message)
        }
    }

    private fun onShowError(message: String) {
        Toast.makeText(context, "APOD API Error: $message", Toast.LENGTH_SHORT).show()
    }

}

PictureOfTheDay ViewModel:

@HiltViewModel
class PictureOfTheDayViewModel @Inject constructor(
    private val getPictureOfTheDayInteractor: GetPictureOfTheDayInteractor,
    private val checkIfPictureIsFavouriteInteractor: CheckIfPictureIsFavouriteInteractor,
    private val addFavouritePictureInteractor: AddFavouritePictureInteractor,
    private val deleteFavouritePictureInteractor: DeleteFavouritePictureInteractor
) : ViewModel() {

    private var _viewState = MutableLiveData<PictureOfTheDayViewState>()
    val viewState: LiveData<PictureOfTheDayViewState>
        get() = _viewState

    private var _viewEvent = LiveEvent<PictureOfTheDayViewEvent>()
    val viewEvent: LiveData<PictureOfTheDayViewEvent>
        get() = _viewEvent

    val isFavouriteState: LiveData<Boolean> = checkIfPictureIsFavouriteInteractor()

    init {
        _viewState.value = PictureOfTheDayViewState()
    }

    fun changeNetworkConnectionStatus(hasNetworkConnection: Boolean) {
        if (viewState.value?.pictureOfTheDay == null) {
            _viewState.value = viewState.newBuilder { copy(hasInternetConnection = hasNetworkConnection) }
            if (hasNetworkConnection) {
                getAstronomyPictureOfTheDay()
            }
        }
    }

    private fun getAstronomyPictureOfTheDay() {
        viewModelScope.launch {
            _viewState.value = viewState.newBuilder { copy(isLoading = true) }
            val response = getPictureOfTheDayInteractor()
            if (response.isSuccessful) {
                val apod = response.body()
                if (apod != null) {
                    _viewState.value = viewState.newBuilder {
                        copy(
                            pictureOfTheDay = apod,
                            isLoading = false
                        )
                    }
                    startCountdownToNextPicture()
                }
            } else {
                Timber.e("Error while getting response from APOD API", response.message())
                _viewEvent.postValue(PictureOfTheDayViewEvent.ShowError(response.message()))
            }
        }
    }

    private fun startCountdownToNextPicture() {
        viewModelScope.launch {
            val timeOfNewPictureUpdate = getDateOfNewPictureUpdate()
            while (true) {
                val currentDate = Calendar.getInstance()
                val diff = timeOfNewPictureUpdate.timeInMillis - currentDate.timeInMillis

                val hours = diff / (1000 * 60 * 60) % 24
                val minutes = diff / (1000 * 60) % 60
                val seconds = (diff / 1000) % 60
                _viewState.value =
                    viewState.newBuilder { copy(timeToNewPicture = "${hours}h ${minutes}min ${seconds}s") }
                delay(1000)
            }
        }
    }

    private fun getDateOfNewPictureUpdate(): Calendar {
        val date = Date()
        val eventDate = Calendar.getInstance()
        eventDate.timeZone = TimeZone.getTimeZone("GMT-04:00")
        eventDate.time = date
        eventDate.add(Calendar.DATE, 1)
        eventDate.set(Calendar.HOUR_OF_DAY, 0)
        eventDate.set(Calendar.MINUTE, 0)
        eventDate.set(Calendar.SECOND, 0)
        return eventDate
    }

    fun onFavouriteButtonClick(pictureBitmap: Bitmap) {
        viewModelScope.launch {
            val apod = viewState.value?.pictureOfTheDay
            if (apod != null) {
                if (isFavouriteState.value == true) {
                    try {
                        deleteFavouritePictureInteractor(apod.date)
                    } catch (e: Exception) {
                        Timber.e(e)
                    }
                } else {
                    try {
                        addFavouritePictureInteractor.invoke(apod, pictureBitmap)
                    } catch (e: Exception) {
                        Timber.e(e)
                    }
                }
            }
        }
    }
}

FavouritePictures Fragment:

@AndroidEntryPoint
class FavouritePictures : Fragment() {

    private var _binding: FragmentFavouritePicturesBinding? = null
    private val binding
        get() = _binding!!

    private val viewModel: FavouritePicturesViewModel by activityViewModels()

    private val adapter by lazy {
        FavouritePicturesListAdapter { picture ->
            findNavController().navigate(FavouritePicturesDirections.openPictureDetails(picture))
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        activity?.window?.statusBarColor = activity?.let { ContextCompat.getColor(it, R.color.red_700) }!!
        _binding = FragmentFavouritePicturesBinding.inflate(layoutInflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        initViews()
        observeViewModel()
    }

    private fun initViews() {
        binding.picturesRecyclerView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
        binding.picturesRecyclerView.adapter = adapter

        binding.toolbar.setOnMenuItemClickListener { item ->
            when (item.itemId) {
                R.id.aboutApp -> {
                    findNavController().navigate(FavouritePicturesDirections.openAbout())
                    true
                }
                else -> false
            }
        }

    }

    private fun observeViewModel() {
        viewModel.picturesList.observe(viewLifecycleOwner, ::bindPictures)
    }

    private fun bindPictures(pictureList: List<FavouritePictureModel>) {
        inflateRecyclerView(pictureList)
        if (pictureList.isEmpty()) {
            binding.noPicturesYetLabel.visibility = View.VISIBLE
        } else {
            binding.noPicturesYetLabel.visibility = View.GONE
        }
    }

    private fun inflateRecyclerView(pictureList: List<FavouritePictureModel>) {
        val list = pictureList.map { picture ->
            picture.id?.let {
                id
                FavouritePicturesItem(
                    id = id,
                    copyright = picture.copyright,
                    date = dateStringToDate(picture.date),
                    explanation = picture.explanation,
                    title = picture.title,
                    url = picture.url,
                    bitmap = picture.bitmap,
                )
            }
        }
        adapter.submitList(list.sortedByDescending { it?.date })
    }

    private fun dateStringToDate(date: String): Date {
        return SimpleDateFormat("yyyy-MM-dd").parse(date)
    }
}

FavouritePictures ViewModel:

@HiltViewModel
class FavouritePicturesViewModel @Inject constructor(
    private val getFavouritePicturesInteractor: GetFavouritePicturesInteractor
) : ViewModel() {

    val picturesList: LiveData<List<FavouritePictureModel>> = getFavouritePicturesInteractor()

    private var _viewState = MutableLiveData<FavouritePicturesViewState>()
    val viewState : LiveData<FavouritePicturesViewState>
        get() = _viewState

}

NavGraph:

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/nav_graph"
    app:startDestination="@id/pictureOfTheDay">

    <fragment
        android:id="@+id/pictureOfTheDay"
        android:name="com.wojciechkula.deepskyapp.presentation.pictureoftheday.PictureOfTheDay"
        android:label="fragment_picture_of_the_day"
        tools:layout="@layout/fragment_picture_of_the_day">
        <action
            android:id="@+id/action_pictureOfTheDay_to_favouritePictures"
            app:destination="@id/favouritePictures" />
    </fragment>
    <fragment
        android:id="@+id/favouritePictures"
        android:name="com.wojciechkula.deepskyapp.presentation.favouritepictures.FavouritePictures"
        android:label="fragment_favourite_pictures"
        tools:layout="@layout/fragment_favourite_pictures">
        <action
            android:id="@+id/openPictureDetails"
            app:destination="@id/pictureDetails"
            app:enterAnim="@android:anim/fade_in"
            app:exitAnim="@android:anim/fade_out"
            app:popEnterAnim="@android:anim/fade_in"
            app:popExitAnim="@android:anim/fade_out" />
        <action
            android:id="@+id/openAbout"
            app:destination="@id/aboutApp"
            app:enterAnim="@android:anim/fade_in"
            app:exitAnim="@android:anim/fade_out"
            app:popEnterAnim="@android:anim/fade_in"
            app:popExitAnim="@android:anim/fade_out" />
    </fragment>
    <fragment
        android:id="@+id/pictureDetails"
        android:name="com.wojciechkula.deepskyapp.presentation.picturedetails.PictureDetails"
        android:label="fragment_picture_details"
        tools:layout="@layout/fragment_picture_details">
        <argument
            android:name="pictureData"
            app:argType="com.wojciechkula.deepskyapp.presentation.favouritepictures.list.FavouritePicturesItem" />
    </fragment>
    <fragment
        android:id="@+id/aboutApp"
        android:name="com.wojciechkula.deepskyapp.presentation.about.AboutFragment"
        android:label="fragment_about"
        tools:layout="@layout/fragment_about" />
</navigation>


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source