Using SharedViewModel and SharedPreferences for Multiple Screens

Yusuf Gültaç
4 min readNov 24, 2023

In Android development, it’s quite common to encounter scenarios where you need to share data across different parts of your application. In this article I will show you the way where I used SharedViewModel and SharedPreferences together to implement a user-like operation in Android application.

The Power of SharedViewModel

The SharedViewModel is a special type of ViewModel that can be shared between multiple fragments or activities. In this example application, we have a SharedPreferences that is responsible for managing the data of the posts that the user has liked

// SharedViewModel is responsible for managing liked posts data and updating it. 
@HiltViewModel
class SharedViewModel @Inject constructor(
private val sharedPrefsHelper: SharedPrefsHelper
) : ViewModel() {
// LiveData to hold the map of liked posts
private val _likedPosts = MutableLiveData<MutableMap<String, Boolean>>(mutableMapOf())
val likedPosts: LiveData<MutableMap<String, Boolean>> = _likedPosts
// Initialize the ViewModel by observing the liked posts LiveData from SharedPreferences
init {
sharedPrefsHelper.likedPostsLiveData.observeForever { likedPosts ->
_likedPosts.value = likedPosts.toMutableMap()
}
}
// Update the like state of a post both in SharedPreferences and SharedViewModel
fun updateLikeState(postId: String, isLiked: Boolean) {
sharedPrefsHelper.updateLikeState(postId, isLiked)
_likedPosts.value?.put(postId, isLiked)
_likedPosts.value = _likedPosts.value?.toMutableMap()
}
}

SharedPreferences in Action

SharedPreferences is an Android API that allows you to save and retrieve persistent key-value pairs of primitive data types. This is perfect for simple data such as the IDs of hte posts that the user has liked.

// SharedPrefsHelper is responsible for managing liked posts data in SharedPreferences
class SharedPrefsHelper @Inject constructor(
@ApplicationContext context: Context
) {
private val sharedPreferences: SharedPreferences =
context.getSharedPreferences("like_prefs", Context.MODE_PRIVATE)

private val _likedPostsLiveData = MutableLiveData<Map<String, Boolean>>()
val likedPostsLiveData: LiveData<Map<String, Boolean>> = _likedPostsLiveData
// Load the liked posts from SharedPreferences when the class is initialized
init {
loadLikedPosts()
}
// Update the like state of a post in SharedPreferences and update the LiveData
fun updateLikeState(postId: String, isLiked: Boolean) {
sharedPreferences.edit().putBoolean(postId, isLiked).apply()
_likedPostsLiveData.value = getAllLikedPosts()
}
...
}

Implementing MainViewModel

The MainViewModel is responsible for fetching all posts and handling user interactions such as liking a post. Other methods ommited for brevity.


// MainViewModel is responsible for fetching all posts and handling user interactions
@HiltViewModel
class MainViewModel @Inject constructor(
private val authRepository: AuthRepository,
private val postRepository: PostRepository,
private val likeRepository: LikeRepository,
private val sharedPrefsHelper: SharedPrefsHelper
) : ViewModel() {

...

fun getAllPost() {
viewModelScope.launch {
try {
val querySnapshot = postRepository.getAllPost().get().await()
val posts = querySnapshot.documents.mapNotNull { document ->
document.toObject(Post::class.java)
}
_postList.postValue(posts)
} catch (e: Exception) {
_errorState.postValue(e.message)
}
}
}

...

// Toggle the like state of a post. If it's already liked, unlike it. If it is not, like it.
suspend fun toggleLikePost(like: Like) {
try {
if (getUserId().isNullOrEmpty()) {
_errorState.postValue("idPost is null or empty")
}
// Check if the post alredy liked by the user
val query =
likeRepository.checkIfPostIsLikedByUser(like.idPost!!, getUserId()).get().await()
if (query.size() > 0) {
// Unliked the post
val idLike = query.documents[0].id
likeRepository.unlikePost(idLike).await()
sharedPrefsHelper.updateLikeState(like.idPost!!, false)
} else {
// Liked the post
likeRepository.likePost(like).await()
sharedPrefsHelper.updateLikeState(like.idPost!!, true)
}
} catch (e: Exception) {
_errorState.postValue(e.message)
}
}
}

Finally, in our fragments, we observe the LiveData from SharedViewModel . When a user likes or unlikes the post, we update the like status in SharedViewModel.

Be careful that SharedViewModel creating instance is different than normal viewmodel instance

@AndroidEntryPoint
class MainFragment : Fragment() {
...
private val viewModel by viewModels<MainViewModel>()
// SharedViewModel instance
private val sharedViewModel: SharedViewModel by activityViewModels()
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

...

sharedViewModel.likedPosts.observe(viewLifecycleOwner) {
postAdapter.notifyDataSetChanged()
}

viewModel.getAllPost()
}



private fun onLikePostClicked(post: Post) {
viewLifecycleOwner.lifecycleScope.launch {
// Get the current like state of the post
val isLiked = sharedViewModel.likedPosts.value?.getOrDefault(post.id!!, false) ?: false
// Toggle the like state in the ViewModel
viewModel.toggleLikePost(
Like(
null,
post.id,
viewModel.getUserId(),
post.title,
post.image1,
post.category,
post.timestamp
)
)
// Update the like state in SharedViewModel
sharedViewModel.updateLikeState(post.id!!, !isLiked)
}
}
}

This code uses SharedViewModel and SharedPreferences to maintain and persist the “like” status of posts across multiple fragments. When a user likes or unlies a post, the like state is updated in SharedPreferences.

Conclusion

In Android development, managing state and persisting data are vital aspects to consider, especially when dealing with user interections such as “like” operations. The combination of SharedViewModel and SharedPreferences provided me an efficient solution to these challanges.

SharedViewModel allows us to share data across the different fragments, ensuring that all parts of our application are observing and reacting to the same state. On the other hand SharedPreferences enables us to persist this state locally, ensuring that “like” status of posts are saved even the application is closed.

This approach promotes consistency in the user experience, as changes in one fragment are immediately reflected in others. It also enhances the resilience of the appllication by preserving the user data against unexpected shutdowns or restarts.

However, it’s crucial to remember that while SharedPreferences is a powerful tool for storing simple key-value pairs, it’s not designed for complex data structures or large data sets. For more complex data persistence needs, other solutions such as Room or an external database might be more suitable. You can read more about ofline caching in Android in my another article in here.

References

https://yusufgltc.medium.com/offline-caching-in-android-477027afa4d4

https://insert-koin.io/docs/reference/koin-android/viewmodel/

--

--