'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:
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?
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 |
|---|



