ViewModel does not save recyclerview data when on configuration changed (device rotate) - android

I just noticed problem earlier in my app, I see the ViewModel inside fragment doesn't save/keep recycler view when I rotate the device, I don't want to use the old method like save data in bundle onSaveInstanceState and restore it, I tried to figure why this problem by printing some logs on each method in fragment lifecycle but I didn't succeed
GIF showing the problem
the ViewModel
#HiltViewModel
class PostViewModel #Inject constructor(
private val mainRepository: MainRepository,
private val dataStoreRepository: DataStoreRepository,
application: Application
) :
AndroidViewModel(application) {
/** ROOM DATABASE */
val readAllPosts: LiveData<List<Item>> = mainRepository.localDataSource.getAllItems().asLiveData()
val postsBySearchInDB: MutableLiveData<List<Item>> = MutableLiveData()
/** RETROFIT **/
var postsResponse: MutableLiveData<NetworkResult<PostList>> = MutableLiveData()
var searchedPostsResponse: MutableLiveData<NetworkResult<PostList>> = MutableLiveData()
var postListResponse: PostList? = null
var postListByLabelResponse: PostList? = null
var searchPostListResponse: PostList? = null
val label = MutableLiveData<String>()
var finalURL: MutableLiveData<String?> = MutableLiveData()
val token = MutableLiveData<String?>()
val currentDestination = MutableLiveData<Int>()
fun getCurrentDestination() {
viewModelScope.launch {
dataStoreRepository.readCurrentDestination.collect {
currentDestination.value = it
}
}
}
val errorCode = MutableLiveData<Int>()
val searchError = MutableLiveData<Boolean>()
var networkStats = false
var backOnline = false
val recyclerViewLayout = dataStoreRepository.readRecyclerViewLayout.asLiveData()
val readBackOnline = dataStoreRepository.readBackOnline.asLiveData()
override fun onCleared() {
super.onCleared()
finalURL.value = null
token.value = null
}
private fun saveBackOnline(backOnline: Boolean) = viewModelScope.launch {
dataStoreRepository.saveBackOnline(backOnline)
}
fun saveCurrentDestination(currentDestination: Int) {
viewModelScope.launch {
dataStoreRepository.saveCurrentDestination(currentDestination)
}
}
fun saveRecyclerViewLayout(layout: String) {
viewModelScope.launch {
dataStoreRepository.saveRecyclerViewLayout(layout)
}
}
fun getPosts() = viewModelScope.launch {
getPostsSafeCall()
}
fun getPostListByLabel() = viewModelScope.launch {
getPostsByLabelSafeCall()
}
fun getItemsBySearch() = viewModelScope.launch {
getItemsBySearchSafeCall()
}
private suspend fun getPostsByLabelSafeCall() {
postsResponse.value = NetworkResult.Loading()
if (hasInternetConnection()) {
try {
val response = mainRepository.remoteDataSource.getPostListByLabel(finalURL.value!!)
postsResponse.value = handlePostsByLabelResponse(response)
} catch (ex: HttpException) {
Log.e(TAG, ex.message + ex.cause)
postsResponse.value = NetworkResult.Error(ex.message.toString())
errorCode.value = ex.code()
} catch (ex: NullPointerException) {
postsResponse.value = NetworkResult.Error("There's no items")
}
} else {
postsResponse.value = NetworkResult.Error("No Internet Connection.")
}
}
private suspend fun getPostsSafeCall() {
postsResponse.value = NetworkResult.Loading()
if (hasInternetConnection()) {
try {
if (finalURL.value.isNullOrEmpty()) {
finalURL.value = "$BASE_URL?key=$API_KEY"
}
val response = mainRepository.remoteDataSource.getPostList(finalURL.value!!)
postsResponse.value = handlePostsResponse(response)
} catch (e: Exception) {
postsResponse.value = NetworkResult.Error(e.message.toString())
if (e is HttpException) {
errorCode.value = e.code()
Log.e(TAG, "getPostsSafeCall: errorCode $errorCode")
Log.e(TAG, "getPostsSafeCall: ${e.message.toString()}")
}
}
} else {
postsResponse.value = NetworkResult.Error("No Internet Connection.")
}
}
private fun handlePostsResponse(response: Response<PostList>): NetworkResult<PostList> {
if (response.isSuccessful) {
if (!(token.value.equals(response.body()?.nextPageToken))) {
token.value = response.body()?.nextPageToken
response.body()?.let { resultResponse ->
Log.d(
TAG, "handlePostsResponse: old token is: ${token.value} " +
"new token is: ${resultResponse.nextPageToken}"
)
finalURL.value = "${BASE_URL}?pageToken=${token.value}&key=${API_KEY}"
Log.e(TAG, "handlePostsResponse finalURL is ${finalURL.value!!}")
for (item in resultResponse.items) {
insertItem(item)
}
return NetworkResult.Success(resultResponse)
}
}
}
if (token.value == null) {
errorCode.value = 400
} else {
errorCode.value = response.code()
}
return NetworkResult.Error(
"network results of handlePostsResponse ${
response.body().toString()
}"
)
}
private fun handlePostsByLabelResponse(response: Response<PostList>): NetworkResult<PostList> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
Log.d(
TAG, "handlePostsByLabelResponse: old token is: ${token.value} " +
"new token is: ${resultResponse.nextPageToken}"
)
finalURL.postValue(
(BASE_URL_POSTS_BY_LABEL + "posts?labels=${label.value}"
+ "&maxResults=20"
+ "&pageToken=")
+ token.value
+ "&key=" + API_KEY
)
if (postListByLabelResponse == null) {
postListByLabelResponse = resultResponse
} else {
val oldPosts = postListByLabelResponse?.items
val newPosts = resultResponse.items
oldPosts?.addAll(newPosts)
}
return NetworkResult.Success(postListByLabelResponse?:resultResponse)
}
}
if (token.value == null) {
errorCode.value = 400
} else {
errorCode.value = response.code()
}
Log.e(TAG, "handlePostsByLabelResponse: final URL ${finalURL.value}")
return NetworkResult.Error(
"network results of handlePostsByLabelResponse ${
response.body().toString()
}"
)
}
private fun hasInternetConnection(): Boolean {
val connectivityManager = getApplication<Application>().getSystemService(
Context.CONNECTIVITY_SERVICE
) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val capabilities =
connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
return when {
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN) -> true
else -> false
}
} else {
val networkInfo = connectivityManager.activeNetworkInfo
return networkInfo != null && networkInfo.isConnectedOrConnecting
}
}
fun showNetworkStats() {
if (!networkStats) {
Toast.makeText(getApplication(), "No Internet connection", Toast.LENGTH_SHORT).show()
saveBackOnline(true)
} else if (networkStats) {
if (backOnline) {
Toast.makeText(getApplication(), "We're back online", Toast.LENGTH_SHORT).show()
saveBackOnline(false)
}
}
}
private fun insertItem(item: Item) {
viewModelScope.launch(Dispatchers.IO) {
mainRepository.localDataSource.insertItem(item)
}
}
private suspend fun getItemsBySearchSafeCall() {
searchedPostsResponse.value = NetworkResult.Loading()
if (!label.value.isNullOrEmpty()) {
finalURL.value = "${BASE_URL}?labels=${label.value}&maxResults=500&key=$API_KEY"
}
Log.e(TAG, "getItemsBySearch: ${finalURL.value}")
if (hasInternetConnection()) {
try {
val response = mainRepository.remoteDataSource.getPostListBySearch(finalURL.value!!)
searchedPostsResponse.value = handlePostsBySearchResponse(response)
} catch (e: Exception) {
searchedPostsResponse.value = NetworkResult.Error(e.message.toString())
}
} else {
searchedPostsResponse.value = NetworkResult.Error("No Internet Connection.")
}
}
private fun handlePostsBySearchResponse(response: Response<PostList>): NetworkResult<PostList> {
return if (response.isSuccessful) {
val postListResponse = response.body()
NetworkResult.Success(postListResponse!!)
} else {
errorCode.value = response.code()
NetworkResult.Error(response.errorBody().toString())
}
}
fun getItemsBySearchInDB(keyword: String) {
Log.d(TAG, "getItemsBySearchInDB: called")
viewModelScope.launch {
val items = mainRepository.localDataSource.getItemsBySearch(keyword)
if (items.isNotEmpty()) {
postsBySearchInDB.value = items
} else {
searchError.value = true
Log.e(TAG, "list is empty")
}
}
}
}
the fragment
#AndroidEntryPoint
class AccessoryFragment : Fragment(), MenuProvider, TitleAndGridLayout {
private var _binding: FragmentAccessoryBinding? = null
private val binding get() = _binding!!
private var searchItemList = arrayListOf<Item>()
private lateinit var postViewModel: PostViewModel
private val titleLayoutManager: GridLayoutManager by lazy { GridLayoutManager(context, 2) }
private val gridLayoutManager: GridLayoutManager by lazy { GridLayoutManager(context, 3) }
private var linearLayoutManager: LinearLayoutManager? = null
private val KEY_RECYCLER_STATE = "recycler_state"
private val mBundleRecyclerViewState by lazy { Bundle() }
private lateinit var adapter: PostAdapter
private var isScrolling = false
var currentItems = 0
var totalItems: Int = 0
var scrollOutItems: Int = 0
private var postsAPiFlag = false
private var keyword: String? = null
private lateinit var networkListener: NetworkListener
private var networkStats = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postViewModel = ViewModelProvider(this)[PostViewModel::class.java]
adapter = PostAdapter(this)
postViewModel.finalURL.value =
BASE_URL_POSTS_BY_LABEL + "posts?labels=Accessory&maxResults=20&key=$API_KEY"
networkListener = NetworkListener()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentAccessoryBinding.inflate(inflater, container, false)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.CREATED)
postViewModel.label.value = "Accessory"
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
Log.d(TAG, "onViewCreated: called")
postViewModel.recyclerViewLayout.observe(viewLifecycleOwner) { layout ->
linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
Log.w(TAG, "onViewCreated getSavedLayout called")
when (layout) {
"cardLayout" -> {
binding.accessoryRecyclerView.layoutManager = linearLayoutManager
adapter.viewType = 0
binding.accessoryRecyclerView.adapter = adapter
}
"cardMagazineLayout" -> {
binding.accessoryRecyclerView.layoutManager = linearLayoutManager
adapter.viewType = 1
binding.accessoryRecyclerView.adapter = adapter
}
"titleLayout" -> {
binding.accessoryRecyclerView.layoutManager = titleLayoutManager
adapter.viewType = 2
binding.accessoryRecyclerView.adapter = adapter
}
"gridLayout" -> {
binding.accessoryRecyclerView.layoutManager = gridLayoutManager
adapter.viewType = 3
binding.accessoryRecyclerView.adapter = adapter
}
}
}
lifecycleScope.launchWhenStarted {
networkListener.checkNetworkAvailability(requireContext()).collect { stats ->
Log.d(TAG, "networkListener: $stats")
postViewModel.networkStats = stats
postViewModel.showNetworkStats()
this#AccessoryFragment.networkStats = stats
if (stats ) {
if (binding.accessoryRecyclerView.visibility == View.GONE) {
binding.accessoryRecyclerView.visibility = View.VISIBLE
}
requestApiData()
} else {
// Log.d(TAG, "onViewCreated: savedInstanceState $savedInstanceState")
noInternetConnectionLayout()
}
}
}
binding.accessoryRecyclerView.onItemClick { _, position, _ ->
val postItem = adapter.differ.currentList[position]
findNavController().navigate(
AccessoryFragmentDirections.actionNavAccessoryToDetailsActivity(
postItem
)
)
}
binding.accessoryRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
currentItems = linearLayoutManager!!.childCount
totalItems = adapter.itemCount
scrollOutItems = linearLayoutManager!!.findFirstVisibleItemPosition()
if ((!recyclerView.canScrollVertically(1) && dy > 0) &&
(isScrolling && currentItems + scrollOutItems >= totalItems && postsAPiFlag)
) {
hideShimmerEffect()
postViewModel.getPostListByLabel()
isScrolling = false
}
}
})
postViewModel.errorCode.observe(viewLifecycleOwner) { errorCode ->
if (errorCode == 400) {
binding.accessoryRecyclerView.setPadding(0, 0, 0, 0)
Toast.makeText(requireContext(), R.string.lastPost, Toast.LENGTH_LONG).show()
binding.progressBar.visibility = View.GONE
} else {
Log.e(TAG, "onViewCreated: ${postViewModel.errorCode.value.toString()} ")
noInternetConnectionLayout()
}
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
Log.d(TAG, "onConfigurationChanged: ${newConfig.orientation}")
Log.d(TAG, "onConfigurationChanged: ${adapter.differ.currentList.toString()}")
Log.d(
TAG,
"onConfigurationChanged: " +
binding.accessoryRecyclerView.layoutManager?.itemCount.toString()
)
}
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
Log.d(TAG, "onViewStateRestored: called")
}
private fun requestApiData() {
showShimmerEffect()
Log.d(TAG, "requestApiData: called")
postViewModel.getPostListByLabel()
postViewModel.postsResponse.observe(viewLifecycleOwner) { response ->
postsAPiFlag = true
when (response) {
is NetworkResult.Success -> {
hideShimmerEffect()
response.data?.let {
binding.progressBar.visibility = View.GONE
// itemArrayList.addAll(it.items)
adapter.differ.submitList(it.items.toList())
}
}
is NetworkResult.Error -> {
hideShimmerEffect()
binding.progressBar.visibility = View.GONE
Log.e(TAG, response.data.toString())
Log.e(TAG, response.message.toString())
}
is NetworkResult.Loading -> {
binding.progressBar.visibility = View.VISIBLE
}
}
}
}
private fun showShimmerEffect() {
binding.apply {
shimmerLayout.visibility = View.VISIBLE
accessoryRecyclerView.visibility = View.INVISIBLE
}
}
private fun hideShimmerEffect() {
binding.apply {
shimmerLayout.stopShimmer()
shimmerLayout.visibility = View.GONE
accessoryRecyclerView.visibility = View.VISIBLE
}
}
private fun changeAndSaveLayout() {
Log.w(TAG, "changeAndSaveLayout: called")
val builder = AlertDialog.Builder(requireContext())
builder.setTitle(getString(R.string.choose_layout))
val recyclerViewLayouts = resources.getStringArray(R.array.RecyclerViewLayouts)
// SharedPreferences.Editor editor = sharedPreferences.edit();
builder.setItems(
recyclerViewLayouts
) { _: DialogInterface?, index: Int ->
try {
when (index) {
0 -> {
adapter.viewType = 0
binding.accessoryRecyclerView.layoutManager = linearLayoutManager
binding.accessoryRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("cardLayout")
}
1 -> {
adapter.viewType = 1
binding.accessoryRecyclerView.layoutManager = linearLayoutManager
binding.accessoryRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("cardMagazineLayout")
}
2 -> {
adapter.viewType = 2
binding.accessoryRecyclerView.layoutManager = titleLayoutManager
binding.accessoryRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("titleLayout")
}
3 -> {
adapter.viewType = 3
binding.accessoryRecyclerView.layoutManager = gridLayoutManager
binding.accessoryRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("gridLayout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
val alertDialog = builder.create()
alertDialog.show()
}
private fun noInternetConnectionLayout() {
binding.apply {
// accessoryRecyclerView.removeAllViews()
Log.d(TAG, "noInternetConnectionLayout: called")
shimmerLayout.stopShimmer()
shimmerLayout.visibility = View.GONE
accessoryRecyclerView.visibility = View.GONE
}
binding.noInternetConnectionLayout.inflate()
binding.noInternetConnectionLayout.let {
if (networkStats) {
it.visibility = View.GONE
}
}
}
override fun onDestroyView() {
super.onDestroyView()
// adapter.isDestroyed = true
linearLayoutManager?.removeAllViews()
// adView.destroy()
linearLayoutManager = null
_binding = null
}
override fun onDetach() {
super.onDetach()
if(linearLayoutManager != null){
linearLayoutManager = null
}
}
override fun tellFragmentToGetItems() {
if (postViewModel.recyclerViewLayout.value.equals("titleLayout")
|| postViewModel.recyclerViewLayout.value.equals("gridLayout")
) {
hideShimmerEffect()
postViewModel.getPostListByLabel()
}
}
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.main, menu)
val searchManager =
requireContext().getSystemService(Context.SEARCH_SERVICE) as SearchManager
val searchView = menu.findItem(R.id.app_bar_search).actionView as SearchView
searchView.setSearchableInfo(searchManager.getSearchableInfo(requireActivity().componentName))
searchView.queryHint = resources.getString(R.string.searchForPosts)
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(keyword: String): Boolean {
if (keyword.isEmpty()) {
Snackbar.make(
requireView(),
"please enter keyword to search",
Snackbar.LENGTH_SHORT
).show()
}
// itemArrayList.clear()
this#AccessoryFragment.keyword = keyword
requestSearchApi(keyword)
return false
}
override fun onQueryTextChange(newText: String): Boolean {
return false
}
})
searchView.setOnCloseListener {
if (keyword.isNullOrEmpty()) {
hideShimmerEffect()
return#setOnCloseListener false
}
if (Utils.hasInternetConnection(requireContext())) {
showShimmerEffect()
postViewModel.postListByLabelResponse = null
searchItemList.clear()
// adapter.differ.submitList(ArrayList())
linearLayoutManager?.removeAllViews()
binding.accessoryRecyclerView.removeAllViews()
// itemArrayList.clear()
adapter.differ.submitList(null)
postViewModel.finalURL.value =
BASE_URL_POSTS_BY_LABEL + "posts?labels=Accessory&maxResults=20&key=$API_KEY"
requestApiData()
// itemArrayList.clear()
// adapter.submitList(itemArrayList)
//====> Here I call the request api method again
Log.d(
TAG,
"setOnCloseListener: called ${adapter.differ.currentList.size.toString()}"
)
// adapter.notifyDataSetChanged()
// binding.progressBar.visibility = View.GONE
//
Log.d(TAG, "setOnCloseListener: ${postViewModel.finalURL.value.toString()}")
//
// adapter.notifyDataSetChanged()
// }
} else {
Log.d(TAG, "setOnCloseListener: called")
adapter.differ.submitList(null)
searchItemList.clear()
noInternetConnectionLayout()
}
false
}
postViewModel.searchError.observe(viewLifecycleOwner) { searchError ->
if (searchError) {
Toast.makeText(
requireContext(),
"There's no posts with this keyword", Toast.LENGTH_LONG
).show()
}
}
}
private fun requestSearchApi(keyword: String) {
if (Utils.hasInternetConnection(requireContext())) {
showShimmerEffect()
postViewModel.finalURL.value =
"${BASE_URL}?labels=Accessory&maxResults=500&key=$API_KEY"
postViewModel.getItemsBySearch()
postViewModel.searchedPostsResponse.observe(viewLifecycleOwner) { response ->
when (response) {
is NetworkResult.Success -> {
postsAPiFlag = false
// adapter.differ.currentList.clear()
if (searchItemList.isNotEmpty()) {
searchItemList.clear()
}
binding.progressBar.visibility = View.GONE
lifecycleScope.launch {
withContext(Dispatchers.Default) {
searchItemList.addAll(response.data?.items?.filter {
it.title.contains(keyword) || it.content.contains(keyword)
} as ArrayList<Item>)
}
}
Log.d(TAG, "requestSearchApi: test size ${searchItemList.size}")
if (searchItemList.isEmpty()) {
// adapter.differ.submitList(null)
Toast.makeText(
requireContext(),
"The search word was not found in any post",
Toast.LENGTH_SHORT
).show()
hideShimmerEffect()
return#observe
} else {
postsAPiFlag = false
// itemArrayList.clear()
adapter.differ.submitList(null)
hideShimmerEffect()
// Log.d(
//// TAG, "requestSearchApi: searchItemList ${searchItemList[0].title}"
// )
adapter.differ.submitList(searchItemList)
// binding.accessoryRecyclerView.scrollToPosition(0)
}
}
is NetworkResult.Error -> {
hideShimmerEffect()
binding.progressBar.visibility = View.GONE
Toast.makeText(
requireContext(),
response.message.toString(),
Toast.LENGTH_SHORT
).show()
Log.e(TAG, "onQueryTextSubmit: $response")
}
is NetworkResult.Loading -> {
binding.progressBar.visibility = View.VISIBLE
}
}
}
} else {
noInternetConnectionLayout()
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return if (menuItem.itemId == R.id.change_layout) {
changeAndSaveLayout()
true
} else false
}
}

Unless I'm missing something (that's a lot of code to go through!) you don't set any data on your adapter until this bit:
private fun requestApiData() {
postViewModel.getPostListByLabel()
postViewModel.postsResponse.observe(viewLifecycleOwner) {
...
adapter.differ.submitList(it.items.toList())
}
And getPostListByLabel() clears the current data in postsResponse
fun getPostListByLabel() = viewModelScope.launch {
getPostsByLabelSafeCall()
}
private suspend fun getPostsByLabelSafeCall() {
postsResponse.value = NetworkResult.Loading()
// fetch data over network and update postsResponse with it later
...
}
So when you first observe it, it's in the NetworkResult.Loading state - any posts you had stored have been wiped.
Your Fragment gets recreated when the Activity is rotated and destroyed - so if you're initialising the ViewModel data contents as part of that Fragment setup (like you're doing here) it's going to get reinitialised every time the Fragment is recreated, and you'll lose the current data.
You'll need to work out a way to avoid that happening - you don't actually want to do that clear-and-fetch whenever a Fragment is created, so you'll have to decide when it should happen. Maybe when the ViewModel is first created (i.e. through the init block), maybe the first time a Fragment calls it (e.g. create an initialised boolean in the VM set to false, check it in the call, set true when it runs). Or maybe just when postsResponse has no value yet (postsResponse.value == null). I don't know the flow of your application so you'll have to work out when to force a fetch and when to keep the data that's already there

Related

Change RecyclerView Layout (from Linear to Grid and reverse) when rotate device automatically

I have 4 different "Card List", "Card Magazine" , "Title" and "Grid" and view holders for each one to relate check my other question here.
now I am trying to change the layout automatically when the device rotates so when orientation is a portrait the layout be LinearLayout "Card layout" and when orientation changes to landscape the layout will be GridLayout, also I have a changeAndSaveLayout method to make the user choose between each layout from option menu
and I save the layout in ViewModel using DataStore and Flow,
The problem
When I rotate the device the RecyclerView and the list is gone and I see the empty screen,
and when I back to portrait the list is back it's back to default layout "cardLayout"
I tried multiple methods like notifyDataSetChanged after changing layout and handle changes in onConfigurationChanged methods but all these methods fails
DataStore class code saveRecyclerViewLayout and readRecyclerViewLayout
private val Context.dataStore by preferencesDataStore("user_preferences")
private const val TAG = "DataStoreRepository"
#ActivityRetainedScoped
class DataStoreRepository #Inject constructor(#ApplicationContext private val context: Context) {
suspend fun saveRecyclerViewLayout(
recyclerViewLayout: String,
) {
datastore.edit { preferences ->
preferences[PreferencesKeys.RECYCLER_VIEW_LAYOUT_KEY] = recyclerViewLayout
}
}
val readRecyclerViewLayout:
Flow<String> = datastore.data.catch { ex ->
if (ex is IOException) {
ex.message?.let { Log.e(TAG, it) }
emit(emptyPreferences())
} else {
throw ex
}
}.map { preferences ->
val recyclerViewLayout: String =
preferences[PreferencesKeys.RECYCLER_VIEW_LAYOUT_KEY] ?: "cardLayout"
recyclerViewLayout
}
}
I used it in ViewModel like the following
#HiltViewModel
class PostViewModel #Inject constructor(
private val mainRepository: MainRepository,
private val dataStoreRepository: DataStoreRepository,
application: Application
) :
AndroidViewModel(application) {
val recyclerViewLayout = dataStoreRepository.readRecyclerViewLayout.asLiveData()
fun saveRecyclerViewLayout(layout: String) {
viewModelScope.launch {
dataStoreRepository.saveRecyclerViewLayout(layout)
}
}
}
PostAdapter Class
class PostAdapter(
private val titleAndGridLayout: TitleAndGridLayout,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var viewType = 0
private val differCallback = object : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
return (oldItem.id == newItem.id)
}
override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
return (oldItem == newItem)
}
}
val differ = AsyncListDiffer(this, differCallback)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (this.viewType) {
CARD -> {
fromCardViewHolder(parent)
}
CARD_MAGAZINE -> {
fromCardMagazineViewHolder(parent)
}
TITLE -> {
fromTitleViewHolder(parent)
}
else -> {
fromGridViewHolder(parent)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item: Item = differ.currentList[position]
when (this.viewType) {
CARD -> if (holder is CardViewHolder) {
holder.bind(item)
}
CARD_MAGAZINE -> if (holder is CardMagazineViewHolder) {
holder.bind(item)
}
TITLE -> if (holder is TitleViewHolder) {
holder.bind(item)
if (position == itemCount - 1)
titleAndGridLayout.tellFragmentToGetItems()
}
GRID -> if (holder is GridViewHolder) {
holder.bind(item)
if (position == itemCount - 1)
titleAndGridLayout.tellFragmentToGetItems()
}
}
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
class CardViewHolder(private val cardLayoutBinding: CardLayoutBinding) :
RecyclerView.ViewHolder(cardLayoutBinding.root) {
fun bind(item: Item) {
val document = Jsoup.parse(item.content)
val elements = document.select("img")
var date: Date? = Date()
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault())
cardLayoutBinding.postTitle.text = item.title
try {
Glide.with(cardLayoutBinding.root).load(elements[0].attr("src"))
.transition(DrawableTransitionOptions.withCrossFade(600))
.placeholder(R.drawable.loading_animation)
.error(R.drawable.no_image)
.into(cardLayoutBinding.postImage)
} catch (e: IndexOutOfBoundsException) {
cardLayoutBinding.postImage.setImageResource(R.drawable.no_image)
}
cardLayoutBinding.postDescription.text = document.text()
try {
date = format.parse(item.published)
} catch (e: ParseException) {
e.printStackTrace()
}
val prettyTime = PrettyTime()
cardLayoutBinding.postDate.text = prettyTime.format(date)
}
}
class CardMagazineViewHolder(private val cardMagazineBinding: CardMagazineBinding) :
RecyclerView.ViewHolder(cardMagazineBinding.root) {
fun bind(item: Item) {
val document = Jsoup.parse(item.content)
val elements = document.select("img")
var date: Date? = Date()
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault())
cardMagazineBinding.postTitle.text = item.title
try {
Glide.with(itemView.context).load(elements[0].attr("src"))
.transition(DrawableTransitionOptions.withCrossFade(600))
.placeholder(R.drawable.loading_animation)
.error(R.drawable.no_image)
.into(cardMagazineBinding.postImage)
} catch (e: IndexOutOfBoundsException) {
cardMagazineBinding.postImage.setImageResource(R.drawable.no_image)
}
try {
date = format.parse(item.published)
} catch (e: ParseException) {
e.printStackTrace()
}
val prettyTime = PrettyTime()
cardMagazineBinding.postDate.text = prettyTime.format(date)
}
}
class TitleViewHolder(private val binding: TitleLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
val document = Jsoup.parse(item.content)
val elements = document.select("img")
binding.postTitle.text = item.title
try {
Glide.with(itemView.context).load(elements[0].attr("src"))
.transition(DrawableTransitionOptions.withCrossFade(600))
.placeholder(R.drawable.loading_animation)
.error(R.drawable.no_image)
.into(binding.postImage)
} catch (e: IndexOutOfBoundsException) {
binding.postImage.setImageResource(R.drawable.no_image)
}
}
}
class GridViewHolder constructor(private val binding: GridLayoutBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
val document = Jsoup.parse(item.content)
val elements = document.select("img")
binding.postTitle.text = item.title
try {
Glide.with(itemView.context).load(elements[0].attr("src"))
.transition(DrawableTransitionOptions.withCrossFade(600))
.placeholder(R.drawable.loading_animation)
.error(R.drawable.no_image)
.into(binding.postImage)
} catch (e: IndexOutOfBoundsException) {
binding.postImage.setImageResource(R.drawable.no_image)
}
}
}
companion object {
private const val CARD = 0
private const val CARD_MAGAZINE = 1
private const val TITLE = 2
private const val GRID = 3
private const val TAG = "POST_ADAPTER"
fun fromCardViewHolder(parent: ViewGroup): CardViewHolder {
val cardLayoutBinding: CardLayoutBinding =
CardLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CardViewHolder(cardLayoutBinding)
}
fun fromCardMagazineViewHolder(parent: ViewGroup): CardMagazineViewHolder {
val cardMagazineBinding: CardMagazineBinding =
CardMagazineBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return CardMagazineViewHolder(cardMagazineBinding)
}
fun fromTitleViewHolder(parent: ViewGroup): TitleViewHolder {
val titleLayoutBinding: TitleLayoutBinding =
TitleLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return TitleViewHolder(titleLayoutBinding)
}
fun fromGridViewHolder(
parent: ViewGroup
): GridViewHolder {
val gridLayoutBinding: GridLayoutBinding =
GridLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return GridViewHolder(gridLayoutBinding)
}
}
init {
setHasStableIds(true)
}
}
and finally the HomeFragment
#AndroidEntryPoint
class HomeFragment : Fragment(), TitleAndGridLayout, MenuProvider {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private var itemArrayList = arrayListOf<Item>()
private var searchItemList = arrayListOf<Item>()
private val postViewModel: PostViewModel by viewModels()
private var linearLayoutManager: LinearLayoutManager? = null
private val titleLayoutManager: GridLayoutManager by lazy {
GridLayoutManager(requireContext(), 2)
}
private val gridLayoutManager: GridLayoutManager by lazy {
GridLayoutManager(requireContext(), 3)
}
private var menuHost: MenuHost? = null
private lateinit var networkListener: NetworkListener
private lateinit var adapter:PostAdapter
private var isScrolling = false
var currentItems = 0
var totalItems: Int = 0
var scrollOutItems: Int = 0
private var postsAPiFlag = false
private val recyclerStateKey = "recycler_state"
private val mBundleRecyclerViewState by lazy { Bundle() }
private var keyword: String? = null
private var orientation: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postViewModel.finalURL.value = "$BASE_URL?key=$API_KEY"
networkListener = NetworkListener()
}
// This property is only valid between onCreateView and
// onDestroyView.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
adapter = PostAdapter(this)
orientation = resources.configuration.orientation
_binding = FragmentHomeBinding.inflate(inflater, container, false)
menuHost = requireActivity()
menuHost?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.CREATED)
postViewModel.recyclerViewLayout.observe(viewLifecycleOwner) { layout ->
linearLayoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
Log.w(TAG, "getSavedLayout called")
Log.w(TAG, "getSavedLayout: orientation ${orientation.toString()}", )
if (orientation == Configuration.ORIENTATION_PORTRAIT) {
when (layout) {
"cardLayout" -> {
//
adapter.viewType = 0
binding.apply {
homeRecyclerView.layoutManager = linearLayoutManager
homeRecyclerView.adapter = adapter
}
}
"cardMagazineLayout" -> {
// binding.loadMoreBtn.visibility = View.VISIBLE
binding.homeRecyclerView.layoutManager = linearLayoutManager
adapter.viewType = 1
binding.homeRecyclerView.adapter = adapter
}
}
} else if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
when (layout) {
"titleLayout" -> {
// binding.loadMoreBtn.visibility = View.GONE
binding.homeRecyclerView.layoutManager = titleLayoutManager
adapter.viewType = 2
binding.homeRecyclerView.adapter = adapter
}
"gridLayout" -> {
binding.homeRecyclerView.layoutManager = gridLayoutManager
adapter.viewType = 3
binding.homeRecyclerView.adapter = adapter
}
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return if (menuItem.itemId == R.id.change_layout) {
changeAndSaveLayout()
true
} else false
}
private fun changeAndSaveLayout() {
// Log.w(TAG, "changeAndSaveLayout: called")
val builder = AlertDialog.Builder(requireContext())
builder.setTitle(getString(R.string.choose_layout))
val recyclerViewPortraitLayout =
resources.getStringArray(R.array.RecyclerViewPortraitLayout)
val recyclerViewLandscapeLayout =
resources.getStringArray(R.array.RecyclerViewLandscapeLayout)
// SharedPreferences.Editor editor = sharedPreferences.edit();
Log.d(TAG, "changeAndSaveLayout: ${orientation.toString()}")
if (orientation == 1) {
builder.setItems(
recyclerViewPortraitLayout
) { _: DialogInterface?, index: Int ->
try {
when (index) {
0 -> {
adapter.viewType = 0
binding.homeRecyclerView.layoutManager = linearLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("cardLayout")
}
1 -> {
adapter.viewType = 1
binding.homeRecyclerView.layoutManager = linearLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("cardMagazineLayout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
} else if (orientation == 2) {
builder.setItems(
recyclerViewLandscapeLayout
) { _: DialogInterface?, index: Int ->
try {
when (index) {
2 -> {
adapter.viewType = 2
binding.homeRecyclerView.layoutManager = titleLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("titleLayout")
}
3 -> {
adapter.viewType = 3
binding.homeRecyclerView.layoutManager = gridLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLayout("gridLayout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
}
val alertDialog = builder.create()
alertDialog.show()
}
}
GIF showing the problem
since long time I was looking for a soultion and i found it and added it to my old project, i was use shared prefernces but in your case i mean data store it will work normally
you need to create two arrays for each orintation
<?xml version="1.0" encoding="utf-8"?>
<resources>
<array name="recyclerViewPortraitList">
<item>Card List</item>
<item>Card Magazine</item>
<item>Title</item>
</array>
<array name="recyclerViewLandscapeList">
<item>Grid with 3 Span</item>
<item>Grid with 4 Span</item>
</array>
</resources>
in your data store will be like the following
private object PreferencesKeys {
var RECYCLER_VIEW_PORTRAIT_LAYOUT_KEY = stringPreferencesKey("recyclerViewPortraitLayout")
var RECYCLER_VIEW_LANDSCAPE_LAYOUT_KEY = stringPreferencesKey("recyclerViewLandscapeLayout")
}
suspend fun saveRecyclerViewPortraitLayout(
recyclerViewLayout: String,
) {
datastore.edit { preferences ->
preferences[PreferencesKeys.RECYCLER_VIEW_PORTRAIT_LAYOUT_KEY] = recyclerViewLayout
}
}
suspend fun saveRecyclerViewLandscapeLayout(recyclerViewLayout: String) {
datastore.edit { preferences ->
preferences[PreferencesKeys.RECYCLER_VIEW_LANDSCAPE_LAYOUT_KEY] = recyclerViewLayout
}
}
val readRecyclerViewPortraitLayout:
Flow<String> = datastore.data.catch { ex ->
if (ex is IOException) {
ex.message?.let { Log.e(TAG, it) }
emit(emptyPreferences())
} else {
throw ex
}
}.map { preferences ->
val recyclerViewLayout: String =
preferences[PreferencesKeys.RECYCLER_VIEW_PORTRAIT_LAYOUT_KEY] ?: "cardLayout"
recyclerViewLayout
}
val readRecyclerViewLandscpaeLayout:
Flow<String> = datastore.data.catch { ex ->
if (ex is IOException) {
ex.message?.let { Log.e(TAG, it) }
emit(emptyPreferences())
} else {
throw ex
}
}.map { preferences ->
val recyclerViewLayout: String =
preferences[PreferencesKeys.RECYCLER_VIEW_LANDSCAPE_LAYOUT_KEY] ?: "gridWith3Span"
recyclerViewLayout
}
in the viewModel
val readRecyclerViewPortraitLayout =
dataStoreRepository.readRecyclerViewPortraitLayout.asLiveData()
val readRecyclerViewLandscapeLayout =
dataStoreRepository.readRecyclerViewLandscpaeLayout.asLiveData()
fun saveRecyclerViewPortraitLayout(layout: String) {
viewModelScope.launch {
dataStoreRepository.saveRecyclerViewPortraitLayout(layout)
}
}
fun saveRecyclerViewLandscapeLayout(layout: String) {
viewModelScope.launch {
dataStoreRepository.saveRecyclerViewLandscapeLayout(layout)
}
}
in adapter change constants
companion object {
private const val CARD = 0
private const val CARD_MAGAZINE = 1
private const val TITLE = 2
private const val GRID_WITH_3_SPAN = 3
private const val GRID_WITH_4_SPAN = 4
}
5.and finally in the fragment or activity you can use it like the following
private fun setUpRecyclerViewLayout() {
if (requireActivity().resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
postViewModel.readRecyclerViewPortraitLayout.observe(viewLifecycleOwner) { layout ->
recyclerViewLayout = layout
when (layout) {
"cardLayout" -> {
adapter.viewType = 0
binding.apply {
homeRecyclerView.layoutManager = linearLayoutManager
homeRecyclerView.adapter = adapter
homeRecyclerView.setHasFixedSize(true)
}
}
"cardMagazineLayout" -> {
binding.homeRecyclerView.layoutManager = linearLayoutManager
adapter.viewType = 1
binding.homeRecyclerView.adapter = adapter
}
"titleLayout" -> {
binding.homeRecyclerView.layoutManager = titleLayoutManager
adapter.viewType = 2
binding.homeRecyclerView.adapter = adapter
}
}
}
} else {
postViewModel.readRecyclerViewLandscapeLayout.observe(viewLifecycleOwner) { layout ->
recyclerViewLayout = layout
when (layout) {
"gridWith3Span" -> {
binding.homeRecyclerView.layoutManager = gridWith3SpanLayoutManager
adapter.viewType = 3
binding.homeRecyclerView.adapter = adapter
}
"gridWith4Span" -> {
binding.homeRecyclerView.layoutManager = gridWith4SpanLayoutManager
adapter.viewType = 4
binding.homeRecyclerView.adapter = adapter
}
}
}
}
}
private fun changeAndSaveLayout() {
val builder = AlertDialog.Builder(requireContext())
builder.setTitle(getString(R.string.choose_layout))
// SharedPreferences.Editor editor = sharedPreferences.edit();
if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
builder.setItems(
resources.getStringArray(R.array.recyclerViewPortraitList)
) { _: DialogInterface?, index: Int ->
try {
when (index) {
0 -> {
adapter.viewType = 0
binding.homeRecyclerView.layoutManager = linearLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewPortraitLayout("cardLayout")
}
1 -> {
adapter.viewType = 1
binding.homeRecyclerView.layoutManager = linearLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewPortraitLayout("cardMagazineLayout")
}
2 -> {
adapter.viewType = 2
binding.homeRecyclerView.layoutManager = titleLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewPortraitLayout("titleLayout")
}
else -> {
throw Exception("Unknown layout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
} else {
builder.setItems(
resources.getStringArray(R.array.recyclerViewLandscapeList)
) { _: DialogInterface?, index: Int ->
try {
when (index) {
0 -> {
adapter.viewType = 3
binding.homeRecyclerView.layoutManager = gridWith3SpanLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLandscapeLayout("gridWith3Span")
}
1 -> {
adapter.viewType = 4
binding.homeRecyclerView.layoutManager = gridWith4SpanLayoutManager
binding.homeRecyclerView.adapter = adapter
postViewModel.saveRecyclerViewLandscapeLayout("gridWith4Span")
}
else -> {
throw Exception("Unknown layout")
}
}
} catch (e: Exception) {
Log.e(TAG, "changeAndSaveLayout: " + e.message)
Log.e(TAG, "changeAndSaveLayout: " + e.cause)
}
}
}
val alertDialog = builder.create()
alertDialog.show()
}
if you setting configuration changes in manifest like that android:configChanges="orientation|screenSize"you will need to call the setUpRecyclerViewLayout() from onConfigurationChanged
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
setUpRecyclerViewLayout()
}

Search Query in android studio(kotlin)

I'm currently following a news app tutorial and I have a problem. When I type in a keyword in the edit text widget, articles related to that keyword shows up in the recycler view but when I erase that keyword to type in another keyword, the articles (in the recycler view) from the previous search query doesn't update and even when I exit the search fragment and open it again,The recycler view remains stagnant instead of disappearing. Can anyone please take a look at my code and let me know what I've done wrong. Thanks in advance.
Here is my code:
Search Fragment
`class SearchNewsFragment : Fragment(R.layout.fragment_search_news) {
lateinit var viewModel: NewsViewModel
lateinit var newsAdapter: NewsAdapter
val TAG = "SearchNewsFragment"
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = (activity as NewsActivity).viewModel
setupRecyclerView()
newsAdapter.setOnItemClickListener {
val bundle = Bundle().apply {
putSerializable("article", it)
}
findNavController().navigate(
R.id.action_searchNewsFragment_to_articleFragment,
bundle
)
}
var job: Job? = null
etSearch.addTextChangedListener { editable ->
job?.cancel()
job = MainScope().launch {
delay(SEARCH_NEWS_TIME_DELAY)
editable?.let {
if(editable.toString().isNotEmpty()) {
viewModel.searchNews(editable.toString())
}
}
}
}
viewModel.searchNews.observe(viewLifecycleOwner, Observer { response ->
when(response) {
is Resource.Success -> {
hideProgressBar()
hideErrorMessage()
response.data?.let { newsResponse ->
newsAdapter.differ.submitList(newsResponse.articles.toList())
val totalPages = newsResponse.totalResults / Constants.QUERY_PAGE_SIZE + 2
isLastPage = viewModel.searchNewsPage == totalPages
if(isLastPage) {
rvSearchNews.setPadding(0, 0, 0, 0)
}
}
}
is Resource.Error -> {
hideProgressBar()
response.message?.let { message ->
Toast.makeText(activity, "An error occured: $message", Toast.LENGTH_LONG).show()
showErrorMessage(message)
}
}
is Resource.Loading -> {
showProgressBar()
}
}
})
btnRetry.setOnClickListener {
if (etSearch.text.toString().isNotEmpty()) {
viewModel.searchNews(etSearch.text.toString())
} else {
hideErrorMessage()
}
}
}
private fun hideProgressBar() {
paginationProgressBar.visibility = View.INVISIBLE
isLoading = false
}
private fun showProgressBar() {
paginationProgressBar.visibility = View.VISIBLE
isLoading = true
}
private fun hideErrorMessage() {
itemErrorMessage.visibility = View.INVISIBLE
isError = false
}
private fun showErrorMessage(message: String) {
itemErrorMessage.visibility = View.VISIBLE
tvErrorMessage.text = message
isError = true
}
var isError = false
var isLoading = false
var isLastPage = false
var isScrolling = false
val scrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val visibleItemCount = layoutManager.childCount
val totalItemCount = layoutManager.itemCount
val isNoErrors = !isError
val isNotLoadingAndNotLastPage = !isLoading && !isLastPage
val isAtLastItem = firstVisibleItemPosition + visibleItemCount >= totalItemCount
val isNotAtBeginning = firstVisibleItemPosition >= 0
val isTotalMoreThanVisible = totalItemCount >= Constants.QUERY_PAGE_SIZE
val shouldPaginate = isNoErrors && isNotLoadingAndNotLastPage && isAtLastItem && isNotAtBeginning &&
isTotalMoreThanVisible && isScrolling
if(shouldPaginate) {
viewModel.searchNews(etSearch.text.toString())
isScrolling = false
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if(newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
isScrolling = true
}
}
}
private fun setupRecyclerView() {
newsAdapter = NewsAdapter()
rvSearchNews.apply {
adapter = newsAdapter
layoutManager = LinearLayoutManager(activity)
addOnScrollListener(this#SearchNewsFragment.scrollListener)
}
}
}`
SearchNewsAdapter
`class SearchNewsAdapter : RecyclerView.Adapter<SearchNewsAdapter.ArticleViewHolder>() {
// Inner class for viewHolder
inner class ArticleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val differCallback = object : DiffUtil.ItemCallback<Article>(){
override fun areItemsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem.url== newItem.url
}
override fun areContentsTheSame(oldItem: Article, newItem: Article): Boolean {
return oldItem == newItem
}
}
val differ = AsyncListDiffer(this, differCallback)
//recyclerViewFunction
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ArticleViewHolder {
return ArticleViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.search_article_preview,parent, false)
)
}
override fun getItemCount(): Int {
return differ.currentList.size
}
override fun onBindViewHolder(holder: ArticleViewHolder, position: Int) {
val article = differ.currentList[position]
holder.itemView.apply{
// Glide.with(this).load(article.urlToImage).into(ivArticleImage)
searchTitle.text = article.title
setOnClickListener{
onItemClickListener?.let{
it(article)
}
}
}
}
//item click listener to single article so that article fragment opens up the webview that shows our items
private var onItemClickListener: ((Article) -> Unit)? = null
fun setOnItemClickListener(listener:(Article) -> Unit){
onItemClickListener = listener
}
}`
NewsViewModel
`class NewsViewModel(
app: Application,
val newsRepository: NewsRepository
) : AndroidViewModel(app) {
val breakingNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var breakingNewsPage = 1
var breakingNewsResponse: NewsResponse? = null
val searchNews: MutableLiveData<Resource<NewsResponse>> = MutableLiveData()
var searchNewsPage = 1
var searchNewsResponse: NewsResponse? = null
var newSearchQuery:String? = null
var oldSearchQuery:String? = null
init {
getBreakingNews("us")
}
fun getBreakingNews(countryCode: String) = viewModelScope.launch {
safeBreakingNewsCall(countryCode)
}
fun searchNews(searchQuery: String) = viewModelScope.launch {
safeSearchNewsCall(searchQuery)
}
private fun handleBreakingNewsResponse(response: Response<NewsResponse>) : Resource<NewsResponse> {
if(response.isSuccessful) {
response.body()?.let { resultResponse ->
breakingNewsPage++
if(breakingNewsResponse == null) {
breakingNewsResponse = resultResponse
} else {
val oldArticles = breakingNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(breakingNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
private fun handleSearchNewsResponse(response: Response<NewsResponse>) : Resource<NewsResponse> {
if(response.isSuccessful) {
response.body()?.let { resultResponse ->
if(searchNewsResponse == null || newSearchQuery != oldSearchQuery) {
searchNewsPage = 1
oldSearchQuery = newSearchQuery
searchNewsResponse = resultResponse
} else {
searchNewsPage++
val oldArticles = searchNewsResponse?.articles
val newArticles = resultResponse.articles
oldArticles?.addAll(newArticles)
}
return Resource.Success(searchNewsResponse ?: resultResponse)
}
}
return Resource.Error(response.message())
}
fun saveArticle(article: Article) = viewModelScope.launch {
newsRepository.upsert(article)
}
fun getSavedNews() = newsRepository.getSavedNews()
fun deleteArticle(article: Article) = viewModelScope.launch {
newsRepository.deleteArticle(article)
}
private suspend fun safeSearchNewsCall(searchQuery: String) {
newSearchQuery = searchQuery
searchNews.postValue(Resource.Loading())
try {
if(hasInternetConnection()) {
val response = newsRepository.searchNews(searchQuery, searchNewsPage)
searchNews.postValue(handleSearchNewsResponse(response))
} else {
searchNews.postValue(Resource.Error("No internet connection"))
}
} catch(t: Throwable) {
when(t) {
is IOException -> searchNews.postValue(Resource.Error("Network Failure"))
else -> searchNews.postValue(Resource.Error("Conversion Error"))
}
}
}
private suspend fun safeBreakingNewsCall(countryCode: String) {
breakingNews.postValue(Resource.Loading())
try {
if(hasInternetConnection()) {
val response = newsRepository.getBreakingNews(countryCode, breakingNewsPage)
breakingNews.postValue(handleBreakingNewsResponse(response))
} else {
breakingNews.postValue(Resource.Error("No internet connection"))
}
} catch(t: Throwable) {
when(t) {
is IOException -> breakingNews.postValue(Resource.Error("Network Failure"))
else -> breakingNews.postValue(Resource.Error("Conversion Error"))
}
}
}
private fun hasInternetConnection(): Boolean {
val connectivityManager = getApplication<NewsApplication>().getSystemService(
Context.CONNECTIVITY_SERVICE
) as ConnectivityManager
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val activeNetwork = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(activeNetwork) ?: return false
return when {
capabilities.hasTransport(TRANSPORT_WIFI) -> true
capabilities.hasTransport(TRANSPORT_CELLULAR) -> true
capabilities.hasTransport(TRANSPORT_ETHERNET) -> true
else -> false
}
} else {
connectivityManager.activeNetworkInfo?.run {
return when(type) {
TYPE_WIFI -> true
TYPE_MOBILE -> true
TYPE_ETHERNET -> true
else -> false
}
}
}
return false
}
}
`
if(editable.toString().isNotEmpty()) { viewModel.searchNews(editable.toString()) }
this line of code prevent the empty query to be processed. So, when you delete everything from the edit text, it will do nothing, hence the result still the same.
even when I exit the search fragment and open it again,The recycler view remains stagnant instead of disappearing.
The search result is strored on NewsViewModel and because the ViewModel is initialized on the NewsActivity, it tied to the activity lifecycle. Even if you destroy the fragment, the search result (the whole ViewModel) will be kept because the activity is still alive. So, when you open back the search Fragment, the LiveData will give you the latest value.

How to add a local filter to calendar using kotlin?

private var modelList: ArrayList<PMDetailResponse.ResultBean>? = null
private var modelListUnchecked: ArrayList<PMDetailResponse.ResultBean>? = null
private lateinit var headerFilter: PMDetailResponse.ResultBean
private var mDetailAdapter: PMDetailAdapter? = null
lateinit var mPreventiveMaintenanceViewModel: PreventiveMaintenanceViewModel
//var mDetailAdapter!!.mAnythingChecked = false
var openedBy: String = ""
var runQuery:String = ""
var noDataReceived = false
companion object {
fun getInstance(): PMWorkOrderDueSoonFragment {
return PMWorkOrderDueSoonFragment()
}
}
private fun handleIncidentListApiResponse(
pmResponses: PMDetailResponse,
isHeaderNeedToAdd: Boolean
) {
mPMResponse = pmResponses
if (pmResponses.result != null) {
var lstResult =
pmResponses.result as ArrayList<PMDetailResponse.ResultBean>
modelList = lstResult
if (lstResult.size > 0) {
setAdapter(
lstResult as ArrayList<PMDetailResponse.ResultBean>,
isHeaderNeedToAdd
)
} else
showNoData(true)
} else
showNoData(true)
}
private fun showNoData(showNoData: Boolean) {
mBindingFragmentGroupBinding.pmAssignedToDeviceRecycerparent.listIncidentNoRecordTv.visibility =
if (showNoData) View.VISIBLE else View.GONE
mBindingFragmentGroupBinding.pmAssignedToDeviceRecycerparent.fragmentIncidentAssigntoHs.visibility =
if (showNoData) View.GONE else View.VISIBLE
}
private fun setAdapter(
lstResult: ArrayList<PMDetailResponse.ResultBean>,
isHeaderNeedToAdd: Boolean
) {
mDetailAdapter = PMDetailAdapter(
lstResult,
this,
true, false, mPreventiveMaintenanceViewModel
)
if (isHeaderNeedToAdd) {
mDetailAdapter!!.addHeader()
}
mBindingFragmentGroupBinding.pmAssignedToDeviceRecycerparent.fragmentIncidentAssigntoDashboardRv.adapter =
mDetailAdapter
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mBindingFragmentGroupBinding =
DataBindingUtil.inflate(
inflater,
R.layout.fragment_pm_assigned_to_device,
container,
false
)
return mBindingFragmentGroupBinding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mPMApiViewModel =
ViewModelProvider(this).get(PMApiViewModel::class.java)
mPreventiveMaintenanceViewModel =
ViewModelProvider(this).get(PreventiveMaintenanceViewModel::class.java)
setUIBackground()
setHasOptionsMenu(true)
mPMApiViewModel.callDuePMApi(PMAssignedStatus.DUE)
callPmDueWOObserver()
openedBy = SharedPreferenceManager.getString(
WeakReference<Context>(activity?.baseContext),
SharedPreferenceConstant.shared_pref_login_user_email
).toString()
}
private fun setUIBackground() {
if (AppConstants.themeColor == Color.WHITE) {
assign_layout.setBackgroundColor(Color.WHITE)
fragment_incident_assignto_dashboard_rv.setBackgroundColor(Color.WHITE)
//linear_layout_assigned_to_hs.setBackgroundColor(Color.WHITE)
//list_incident_no_record_tv.setTextColor(Color.BLACK)
} else {
assign_layout.setBackgroundColor(Color.BLACK)
fragment_incident_assignto_dashboard_rv.setBackgroundColor(Color.BLACK)
//linear_layout_assigned_to_hs.setBackgroundColor(Color.BLACK)
//list_incident_no_record_tv.setTextColor(Color.WHITE)
}
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
mMenu = menu
clearMenu()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
val exportMenuItem: MenuItem = menu.findItem(R.id.export_filter)
var feedBackSubmitTextView =
exportMenuItem.actionView.findViewById<Button>(R.id.button_export_bt)
feedBackSubmitTextView.setText(getString(R.string.dispatch_pm_to_me))
val exportActionView: View = exportMenuItem.getActionView()
exportActionView.setOnClickListener(object : View.OnClickListener {
override fun onClick(p0: View?) {
onOptionsItemSelected(exportMenuItem)
}
})
/* val awesomeMenuItem: MenuItem = menu.findItem(R.id.run_filter)
val awesomeActionView: View = awesomeMenuItem.getActionView()
awesomeActionView.setOnClickListener(object : View.OnClickListener {
override fun onClick(p0: View?) {
onOptionsItemSelected(awesomeMenuItem)
}
})*/
menu.findItem(R.id.run_filter).isVisible = true
menu.findItem(R.id.save_filter).isVisible = true
menu.findItem(R.id.save_filter).setIcon(resources.getDrawable(R.drawable.calendar));
menu.findItem(R.id.icons_menu).isVisible = true
menu.findItem(R.id.icons_menu).subMenu.findItem(R.id.action_barcode_scanner).isVisible =
false
menu.findItem(R.id.icons_menu).subMenu.findItem(R.id.attach).isVisible = false
menu.findItem(R.id.icons_menu).subMenu.findItem(R.id.action_soti_device_details).isVisible =
false
menu.findItem(R.id.icons_menu).subMenu.findItem(R.id.run_pm_filter).isVisible = false
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.export_filter -> {
Log.e("HistoricalFrag", "Export Button Selected")
if(!noDataReceived) {
if (mDetailAdapter!!.mAnythingChecked) {
val jsonArray = updateList()
val pmDispatchRequest: PMDispatchRequestArray = PMDispatchRequestArray(jsonArray)
Log.i("jsonArray", "jsonArray::$jsonArray")
//val workOrderObj = JSONObject()
// workOrderObj.put("DispatchInfoList", jsonArray)
//val jsonStr = jsonArray.toString()
//Log.i("TAG", "jsonStr::$jsonStr")
mDetailAdapter!!.mAnythingChecked = false
if (!jsonArray.isEmpty()) {
mPMApiViewModel.callPMDispatchToMe(pmDispatchRequest)
setObserverForDispatchToMeAPI()
} else {
showToast("Please Select The CheckBox")
}
} else {
showToast("Please Select The CheckBox")
}
}else{
showToast("Not allowed")
}
return true
}
R.id.run_filter -> {
if(!noDataReceived) {
runQuery = ""
mDetailAdapter!!.mAnythingChecked = false
var runQuery = runPMFilter()
if (runQuery.isNotEmpty()) {
mDetailAdapter!!.removeHeader()
mDetailAdapter!!.clear()
callPmDueFilterObserver()
mPMApiViewModel.callFilterDuePMApi(runQuery, PMAssignedStatus.FILTER)
} else {
showToast("No Filter Provided")
}
}else{
showToast("Not allowed")
}
return true
}
R.id.save_filter->{
pickFromDateTime()
return true
}
else -> super.onOptionsItemSelected(item)
}
}
private fun clearMenu() {
mMenu.findItem(R.id.action_search).setVisible(false)
mMenu.findItem(R.id.action_settings).setVisible(false)
mMenu.findItem(R.id.action_incident).setVisible(false)
mMenu.findItem(R.id.export_filter).setVisible(true)
mMenu.findItem(R.id.run_filter).setVisible(true)
mMenu.findItem(R.id.icons_menu).setVisible(false)
}
private fun callPmDueWOObserver() {
if (mPMApiViewModel.callDuePMWorkOrderResponseObserver()
?.hasActiveObservers()!!
)
mPMApiViewModel.callDuePMWorkOrderResponseObserver()?.removeObserver(
pmResponseObserver
)!!
pmResponseObserver = Observer { value ->
if (mPMApiViewModel.mIsResponseforDuePmWorkOrder) {
if (value.apiStatus) {
showProgressDialog()
} else {
mPMApiViewModel.mIsResponseforDuePmWorkOrder = false
hideProgressDialog()
try {
if (value.response != null && !value.response.equals(getString(R.string.server_error))) {
if (value.response is PMDetailResponse) {
val mPmList =
value.response as PMDetailResponse
if (mPmList.result?.size!! > 0) {
handleIncidentListApiResponse(mPmList, true)
} else
showDialog(
getString(R.string.error_msg),
getString(R.string.no_response_server),
true
)
}
} else {
showToast(getString(R.string.server_error))
noDataReceived = true
showNoData(true)
}
} catch (ex: SocketTimeoutException) {
ex.printStackTrace()
LogOperation.getLogger().logInfo(
LogLevel.INFO_LOG,
"callPmDueWOObserver - Socket Time Out Exception : $ex"
)
showToast(getString(R.string.socket_timeout_error))
} catch (ex: Exception) {
ex.printStackTrace()
LogOperation.getLogger().logInfo(
LogLevel.INFO_LOG,
"callPmDueWOObserver - Exception : $ex"
)
showToast(getString(R.string.exception_error))
}
}
}
}
mPMApiViewModel.callDuePMWorkOrderResponseObserver()
?.observe(viewLifecycleOwner, pmResponseObserver)
}
private fun callPmDueFilterObserver() {
if (mPMApiViewModel.callFilterPMWorkOrderResponseObserver()
?.hasActiveObservers()!!
)
mPMApiViewModel.callFilterPMWorkOrderResponseObserver()?.removeObserver(
pmResponseObserver
)!!
pmResponseObserver = Observer { value ->
if (mPMApiViewModel.mIsResponseForFilterDuePMWO) {
if (value.apiStatus) {
showProgressDialog()
} else {
mPMApiViewModel.mIsResponseForFilterDuePMWO = false
hideProgressDialog()
try {
if (value.response != null && !value.response.equals(getString(R.string.server_error))) {
if (value.response is PMDetailResponse) {
val mPmList =
value.response as PMDetailResponse
if (mPmList.result?.size!! > 0) {
//handleIncidentListApiResponse(mPmList, true)
mDetailAdapter!!.clear()
mDetailAdapter!!.removeHeader()
mDetailAdapter!!.updateData(mPmList.result as ArrayList<PMDetailResponse.ResultBean>)
mDetailAdapter!!.addHeader()
mDetailAdapter!!.notifyDataSetChanged()
} else
showDialog(
getString(R.string.error_msg),
getString(R.string.no_response_server),
true
)
}
} else {
showToast(getString(R.string.server_error_loading_previous))
changeFragment(R.id.nav_drawer_pm_due_soon,Bundle(),false)
}
} catch (ex: SocketTimeoutException) {
ex.printStackTrace()
LogOperation.getLogger().logInfo(
LogLevel.INFO_LOG,
"callPmDueFilterObserver - Socket Time Out Exception : $ex"
)
showToast(getString(R.string.socket_timeout_error))
} catch (ex: Exception) {
ex.printStackTrace()
LogOperation.getLogger().logInfo(
LogLevel.INFO_LOG,
"callPmDueFilterObserver - Exception : $ex"
)
showToast(getString(R.string.exception_error))
}
}
}
}
mPMApiViewModel.callFilterPMWorkOrderResponseObserver()
?.observe(viewLifecycleOwner, pmResponseObserver)
}
private fun setObserverForDispatchToMeAPI() {
mPMApiViewModel.dispatchToMe()
?.observe(this, Observer { value ->
if (mPMApiViewModel.mIsFromDispatchApiCall) {
if (value.apiStatus) {
showProgressDialog()
} else {
mPMApiViewModel.mIsFromDispatchApiCall = false
hideProgressDialog()
try {
if (value.response is UpdateIncidentMessage) {
val responseValue = value.response as UpdateIncidentMessage
Log.d("CheckMyResponseValue", " - " + responseValue)
if (responseValue.result?.http_status?.equals("201")!!) {
if (modelListUnchecked!!.size == 0) {
mBindingFragmentGroupBinding.pmAssignedToDeviceRecycerparent.fragmentIncidentAssigntoDashboardRv!!.visibility =
View.GONE
}
//showToast("Dispatched ")
showDispatchDialog(
resources.getString(R.string.dispatch_msg),
responseValue.result?.status_message,true
)
} else if (responseValue.result?.http_status?.equals("400")!! ||
responseValue.result?.http_status?.equals("501")!!) {
mDetailAdapter!!.notifyDataSetChanged()
showDispatchDialog(
resources.getString(R.string.error_msg),
responseValue.result?.status_message,false
)
}
else {
showDispatchDialog(
resources.getString(R.string.error_msg),
resources.getString(R.string.update_error_msg),
false
)
}
}
} catch (ex: SocketTimeoutException) {
ex.printStackTrace()
LogOperation.getLogger().logInfo(
LogLevel.INFO_LOG,
"setObserverForDispatchToMeAPI - Socket Time Out Exception : $ex"
)
} catch (ex: Exception) {
ex.printStackTrace()
LogOperation.getLogger().logInfo(
LogLevel.INFO_LOG,
"setObserverForDispatchToMeAPI - Exception : $ex"
)
}
}
}
})
}
fun showDialog(title: String?, message: String?, stayOnSameScreen: Boolean) {
val dialogBuilder = AlertDialog.Builder(activity)
dialogBuilder.setMessage(message)
.setCancelable(false)
.setPositiveButton("Ok", DialogInterface.OnClickListener { dialog, id ->
dialog.dismiss()
if (stayOnSameScreen) {
backToPreviousFragment(true)
}
})
val alert = dialogBuilder.create()
alert.setTitle(title)
alert.show()
}
override fun onItemChangeListener(
position: Int,
model: PMDetailResponse.ResultBean
) {
try {
modelList!![position] = model
mDetailAdapter!!.mAnythingChecked = true
} catch (e: Exception) {
}
}
private fun updateList(): ArrayList<PMDispatchRequest> {
val temp = ArrayList<PMDetailResponse.ResultBean>()
val jsonArray = ArrayList<PMDispatchRequest>()
try {
for (i in modelList!!.indices) {
if (!modelList!![i].isChecked) {
temp.add(modelList!![i])
}else{
Log.i("TAG", "Number[" + i + "]::" + modelList!![i].number)
jsonArray?.add(createDispatchPMToMeQuery(modelList!![i].number))
}
}
} catch (e: Exception) {
}
modelListUnchecked = temp
/* mDetailAdapter!!.setModel(modelListUnchecked as ArrayList<PMDetailResponse.ResultBean>)
mDetailAdapter!!.notifyDataSetChanged()*/
return jsonArray
}
private fun createDispatchPMToMeQuery(number: String?): PMDispatchRequest {
/*{
"u_work_order_number": "WO0018180",
"u_subcategory": "Preventative Maintenance",
"u_category": "Device Maintenance",
"u_assign_to": "Kchawla#plan-group.com"
}*/
val workOrder = PMDispatchRequest(number,"Preventative Maintenance","Device Maintenance",openedBy)
return workOrder
}
fun createFilter(filterOptions: String, filterColumn: String) {
if(filterOptions != ""){
runQuery = if(filterOptions.startsWith("*")) {
if(runQuery == "")
filterColumn+"LIKE"+filterOptions.substring(1)
else
"$runQuery^"+filterColumn+"LIKE"+filterOptions.substring(1)
}else {
if (runQuery == "")
filterColumn+"STARTSWITH"+filterOptions
else
"$runQuery^"+filterColumn+"STARTSWITH"+filterOptions
}
}
}
fun createPMFrequencyFilter(filterOptions: String, filterColumn: String) {
var pmFilter = ""
if(filterOptions != ""){
pmFilter = pmFrequency(filterOptions)
runQuery = if(pmFilter.startsWith("*")) {
if(runQuery == "")
filterColumn+"IN"+ pmFilter.substring(1)
else
"$runQuery^"+filterColumn+"IN"+pmFilter.substring(1)
}else {
if (runQuery == "")
filterColumn+"IN"+pmFilter
else
"$runQuery^"+filterColumn+"IN"+pmFilter
}
}
}
private fun runPMFilter(): String {
createFilter(mDetailAdapter!!.searchedNumber,"number")
createFilter(mDetailAdapter!!.searchedCI,"cmdb_ci.name")
createFilter(mDetailAdapter!!.searchedDeviceLocation,"location.name")
createFilter(mDetailAdapter!!.searchedParent,"location.parent.name")
createFilter(mDetailAdapter!!.searchedDispatchLocation,"u_dispatch_location.name")
createFilter(mDetailAdapter!!.searchedRequestedDueBy,"requested_due_by")
createPMFrequencyFilter(mDetailAdapter!!.searchedPMFrequency,"u_pm_frequency")
createFilter(mDetailAdapter!!.searchedShortDesc,"short_description")
Log.i("TAG", "runQuery::"+runQuery)
if(runQuery.isNotEmpty()){
//val runQueryLength = runQuery.length
runQuery = runQuery.trim()
runQuery += "/"
}else{
runQuery = ""
}
Log.i("finalQuery", "runQueryFinal::"+runQuery)
return runQuery
}
fun pmFrequency(pmFrequency:String):String{
var pmFreq = ""
if(pmFrequency.startsWith("*")){
pmFreq = pmFrequency.substring(1)
}
else{
pmFreq = pmFrequency
}
if("Monthly".contains(pmFreq,ignoreCase = true)
&& "Yearly".contains(pmFreq,ignoreCase = true)){
pmFreq = "Yearly,Monthly"
}else if("Yearly".contains(pmFreq,ignoreCase = true)){
pmFreq = "Yearly"
}else if("Monthly".contains(pmFreq,ignoreCase = true)){
//pmFreq = "*Monthly"
pmFreq = "Monthly"
}
return pmFreq
}
fun showDispatchDialog(title: String?, message: String?,isSuccess:Boolean) {
val dialogBuilder = AlertDialog.Builder(activity)
dialogBuilder.setMessage(message)
.setCancelable(false)
.setPositiveButton(getString(R.string.ok), DialogInterface.OnClickListener { dialog, id ->
if(!isSuccess) {
mDetailAdapter!!.setModel(modelList as ArrayList<PMDetailResponse.ResultBean>)
mDetailAdapter!!.notifyDataSetChanged()
}
else{
mDetailAdapter!!.setModel(modelListUnchecked as ArrayList<PMDetailResponse.ResultBean>)
mDetailAdapter!!.notifyDataSetChanged()
}
dialog.dismiss()
changeFragment(R.id.nav_drawer_pm_due_soon,Bundle(),false)
//backToPreviousFragment(false)
})
val alert = dialogBuilder.create()
alert.setTitle(title)
alert.show()
}
private fun pickFromDateTime() {
val currentDateTime = Calendar.getInstance()
val startYear = currentDateTime.get(Calendar.YEAR)
val startMonth = currentDateTime.get(Calendar.MONTH)
val startDay = currentDateTime.get(Calendar.DAY_OF_MONTH)
val startHour = currentDateTime.get(Calendar.HOUR_OF_DAY)
val startMinute = currentDateTime.get(Calendar.MINUTE)
var mFromDatePicker = DatePickerDialog(requireContext(), DatePickerDialog.OnDateSetListener { _, year, month, day ->
TimePickerDialog(requireContext(), TimePickerDialog.OnTimeSetListener { _, hour, minute ->
val pickedDateTime = Calendar.getInstance()
pickedDateTime.set(year, month, day, hour, minute)
val formatterDateTime = SimpleDateFormat("yyyy-MM-dd HH mm ss")
val formatterDate = SimpleDateFormat("yyyy-MM-dd")
var selectedFromDateTime = formatterDateTime.format(pickedDateTime.time)
var selectedFromDate = formatterDate.format(pickedDateTime.time)
Log.i(ContentValues.TAG, "selectedFromDateTime::$selectedFromDateTime")
pickToDateTime(selectedFromDateTime)
}, startHour, startMinute, false).show()
/*val pickedDateTime = Calendar.getInstance()
pickedDateTime.set(year, month, day)
doSomethingWith(pickedDateTime)*/
}, startYear, startMonth, startDay);
mFromDatePicker.setTitle("FROM");
mFromDatePicker.datePicker.maxDate = System.currentTimeMillis();
mFromDatePicker.show()
}
private fun pickToDateTime(selectedDateTime1: String) {
val currentDateTime = Calendar.getInstance()
val startYear = currentDateTime.get(Calendar.YEAR)
val startMonth = currentDateTime.get(Calendar.MONTH)
val startDay = currentDateTime.get(Calendar.DAY_OF_MONTH)
val startHour = currentDateTime.get(Calendar.HOUR_OF_DAY)
val startMinute = currentDateTime.get(Calendar.MINUTE)
var mToDatePicker = DatePickerDialog(requireContext(), DatePickerDialog.OnDateSetListener { _, year, month, day ->
TimePickerDialog(requireContext(), TimePickerDialog.OnTimeSetListener { _, hour, minute ->
val pickedDateTime = Calendar.getInstance()
pickedDateTime.set(year, month, day, hour, minute)
val formatterDateTime = SimpleDateFormat("yyyy-MM-dd HH mm ss")
var selectedFromDateTime = formatterDateTime.format(pickedDateTime.time)
Log.i(ContentValues.TAG, "selectedToDateTime::$selectedFromDateTime")
}, startHour, startMinute, false).show()
/* val pickedToDateTime = Calendar.getInstance()
pickedToDateTime.set(year, month, day)
doCallChangeFragment(selectedDateTime, pickedToDateTime)*/
}, startYear, startMonth, startDay);
mToDatePicker.setTitle("TO");
mToDatePicker.datePicker.maxDate = System.currentTimeMillis();
mToDatePicker.show()
}
}
This code is being used to pick up from date,time and to date, time using calender feature for filtering data for particular period but the data is not getting filtered, the data for all the month is being displayed .So I need to add local filter for filtering data according to specific date and time using calendar feature.

Unable to update content of recycleview based on user's location

I've been experiencing a strange bug when retrieving a user location and displaying data based on the location retrieved in a recycler view. For context, whenever the application is started from fresh (no permissions granted) can retrieve the location. However, it doesn't display the expected content in the recycler view unless I close the application or press the bottom navigation bar.
Demonstration:
https://i.imgur.com/9kc1Zxc.gif
Fragment
const val TAG = "ForecastFragment"
#AndroidEntryPoint
class ForecastFragment : Fragment(), SearchView.OnQueryTextListener,
SwipeRefreshLayout.OnRefreshListener{
private val viewModel: WeatherForecastViewModel by viewModels()
private var _binding: FragmentForecastBinding? = null
private val binding get() = _binding!!
private lateinit var forecastAdapter: ForecastAdapter
private lateinit var searchMenuItem: MenuItem
private lateinit var searchView: SearchView
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private var currentQuery: String? = null
private lateinit var client: FusedLocationProviderClient
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.forecast_list_menu, menu)
searchMenuItem = menu.findItem(R.id.menu_search)
searchView = searchMenuItem.actionView as SearchView
searchView.isSubmitButtonEnabled = true
searchView.setOnQueryTextListener(this)
return super.onCreateOptionsMenu(menu, inflater)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentForecastBinding.inflate(inflater, container, false)
binding.lifecycleOwner = this
client = LocationServices.getFusedLocationProviderClient(requireActivity())
swipeRefreshLayout = binding.refreshLayoutContainer
setupRecyclerView()
getLastLocation()
updateSupportActionbarTitle()
swipeRefreshLayout.setOnRefreshListener(this)
return binding.root
}
private fun updateSupportActionbarTitle() {
viewModel.queryMutable.observe(viewLifecycleOwner, Observer { query ->
(activity as AppCompatActivity).supportActionBar?.title = query
})
}
private fun setupRecyclerView() {
forecastAdapter = ForecastAdapter()
binding.forecastsRecyclerView.apply {
adapter = forecastAdapter
layoutManager = LinearLayoutManager(activity)
}
}
private fun requestWeatherApiData() {
lifecycleScope.launch {
viewModel.weatherForecast.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
// nothing for now
swipeRefreshLayout.isRefreshing = false
}
}
})
}
}
private fun searchWeatherApiData(searchQuery: String) {
viewModel.searchWeatherForecast(viewModel.applySearchQuery(searchQuery))
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
private fun getWeatherApiDataLocation(city: String) {
viewModel.weatherForecastLocation(viewModel.applyLocationQuery(city))
viewModel.weatherForecastLocation.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
forecastAdapter.notifyDataSetChanged()
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
private fun fetchForecastAsync(query: String?) {
if (query == null)
requestWeatherApiData()
else
searchWeatherApiData(query)
}
override fun onQueryTextSubmit(query: String?): Boolean {
if (query != null) {
currentQuery = query
viewModel.queryMutable.value = query
searchWeatherApiData(query)
}
return true
}
override fun onQueryTextChange(newText: String?): Boolean {
return true
}
override fun onRefresh() {
currentQuery = viewModel.queryMutable.value
fetchForecastAsync(currentQuery)
}
private fun isLocationEnabled(): Boolean {
val locationManager: LocationManager =
requireActivity().getSystemService(Context.LOCATION_SERVICE) as LocationManager
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|| locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
}
private fun checkPermissions(): Boolean {
if (ActivityCompat.checkSelfPermission(requireContext(),
Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(requireContext(),
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED){
return true
}
return false
}
private fun requestPermissions() {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION), PERMISSION_ID)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == PERMISSION_ID) {
if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
getLastLocation()
}
}
}
#SuppressLint("MissingPermission")
private fun getLastLocation() {
if (checkPermissions()) {
if (isLocationEnabled()) {
client.lastLocation.addOnCompleteListener(requireActivity()) { task ->
val location: Location? = task.result
if (location == null) {
requestNewLocationData()
} else {
val geoCoder = Geocoder(requireContext(), Locale.getDefault())
val addresses = geoCoder.getFromLocation(
location.latitude, location.longitude, 1)
val city = addresses[0].locality
viewModel.queryMutable.value = city
getWeatherApiDataLocation(city)
}
}
} else {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
startActivity(intent)
}
} else {
requestPermissions()
}
}
#SuppressLint("MissingPermission")
private fun requestNewLocationData() {
val mLocationRequest = LocationRequest.create().apply {
priority = LocationRequest.PRIORITY_HIGH_ACCURACY
interval = 0
fastestInterval = 0
numUpdates = 1
}
client = LocationServices.getFusedLocationProviderClient(requireActivity())
client.requestLocationUpdates(
mLocationRequest, mLocationCallback,
Looper.myLooper()
)
}
private val mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
val mLastLocation: Location = locationResult.lastLocation
mLastLocation.latitude.toString()
mLastLocation.longitude.toString()
}
}
}
View Model
#HiltViewModel
class WeatherForecastViewModel
#Inject constructor(
private val repository: WeatherForecastRepository,
application: Application) :
AndroidViewModel(application) {
private val unit = "imperial"
private var query = "Paris"
val weatherForecast: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
var searchWeatherForecastResponse: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
val weatherForecastLocation: MutableLiveData<Resource<WeatherForecastResponse>> = MutableLiveData()
var queryMutable: MutableLiveData<String> = MutableLiveData()
init {
//queryMutable.value = query
//getWeatherForecast(queryMutable.value.toString(), unit)
}
private fun getWeatherForecast(query: String, units: String) =
viewModelScope.launch {
weatherForecast.postValue(Resource.Loading())
val response = repository.getWeatherForecast(query, units)
weatherForecast.postValue(weatherForecastResponseHandler(response))
}
private suspend fun searchWeatherForecastSafeCall(searchQuery: Map<String, String>) {
searchWeatherForecastResponse.postValue(Resource.Loading())
val response = repository.searchWeatherForecast(searchQuery)
searchWeatherForecastResponse.postValue(weatherForecastResponseHandler(response))
}
private suspend fun getWeatherForecastLocationSafeCall(query: Map<String, String>) {
weatherForecastLocation.postValue(Resource.Loading())
val response = repository.getWeatherForecastLocation(query)
weatherForecastLocation.postValue(weatherForecastResponseHandler(response))
}
fun searchWeatherForecast(searchQuery: Map<String, String>) =
viewModelScope.launch {
searchWeatherForecastSafeCall(searchQuery)
}
fun weatherForecastLocation(query: Map<String, String>) =
viewModelScope.launch {
getWeatherForecastLocationSafeCall(query)
}
fun applySearchQuery(searchQuery: String): HashMap<String, String> {
val queries: HashMap<String, String> = HashMap()
queries[QUERY_CITY] = searchQuery
queries[QUERY_UNITS] = unit
queries[QUERY_COUNT] = API_COUNT
queries[QUERY_API] = API_KEY
return queries
}
fun applyLocationQuery(query: String): HashMap<String, String> {
val queries: HashMap<String, String> = HashMap()
queries[QUERY_CITY] = query
queries[QUERY_UNITS] = unit
queries[QUERY_COUNT] = API_COUNT
queries[QUERY_API] = API_KEY
return queries
}
private fun weatherForecastResponseHandler(
response: Response<WeatherForecastResponse>): Resource<WeatherForecastResponse> {
if (response.isSuccessful) {
response.body()?.let { result ->
return Resource.Success(result)
}
}
return Resource.Error(response.message())
}
}
I feel the problem lies in the way, you are setting up your observers, you are setting them up in a function call, I believe that is wrong and that is setting up multiple observers for the same thing. It should just be setup once.
So I suggest you take out your observers to a separate function and call it once like observeProperties()
So your code will be like this
private fun observeProperties() {
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
viewModel.weatherForecastLocation.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
forecastAdapter.notifyDataSetChanged()
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
viewModel.searchWeatherForecastResponse.observe(viewLifecycleOwner, Observer { response ->
when (response) {
is Resource.Success -> {
response.data?.let { forecastResponse ->
forecastAdapter.diff.submitList(forecastResponse.list)
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Error -> {
response.msg?.let { msg ->
Log.e(TAG, "An error occurred : $msg")
swipeRefreshLayout.isRefreshing = false
}
}
is Resource.Loading -> {
swipeRefreshLayout.isRefreshing = false
}
}
})
}
Now you can call this method at the top as such
setupRecyclerView()
getLastLocation()
updateSupportActionbarTitle()
swipeRefreshLayout.setOnRefreshListener(this)
observeProperties()
Your other methods simply will be now the queries like this
private fun getWeatherApiDataLocation(city: String) {
viewModel.weatherForecastLocation(viewModel.applyLocationQuery(city))
}
private fun searchWeatherApiData(searchQuery: String) {
viewModel.searchWeatherForecast(viewModel.applySearchQuery(searchQuery))
}

Update view adapter recyclerView mvvm (play sound and update view)

I have a list of user record sound
The user can click on the list and play the audio file
  How can I handle the MediaPlayer and the RecyclerView item in the Seekbar as well in the MVVM correctly?
That is, when the user clicks, the visitor changes the item and updates itself, and when it is clicked on an item again, it updates itself.
I did it now, but unfortunately, to the dirtiest possible form
activity code :
class SoundListActivity : BaseActivity(), Observer<List<VoiceEntity>>, VoiceAdapter.OnClickItemListener,
OnMultiSelectVoiceListener {
private lateinit var viewModel: VoiceViewModel
private val adapter = VoiceAdapter()
private val player = MediaPlayer()
private var positionPlayItem = -1
companion object {
fun start(context: Context) {
context.startActivity(Intent(context, SoundListActivity::class.java))
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sound_list)
viewModel = ViewModelProviders.of(this).get(VoiceViewModel::class.java)
viewModel.mutableList!!.observe(this, this)
adapter.onItemClickListener = this
adapter.listenerMultiSelect = this
recycler.layoutManager = LinearLayoutManager(this)
recycler.adapter = adapter
fabAdd.setOnClickListener {
stopPlay()
RecordSoundActivity.start(this)
}
toolbar.setIconLeftListener(View.OnClickListener {
stopPlay()
finish()
})
}
override fun onChanged(list: List<VoiceEntity>?) {
this.progressBar.visibility = View.GONE
this.layoutEmptyState.visibility = View.INVISIBLE
this.adapter.removeAll()
if (list == null || list.isEmpty()) {
layoutEmptyState.visibility = View.VISIBLE
return
}
adapter.addItems(ArrayList(list))
}
override fun onClickItem(item: VoiceEntity, position: Int) {
if (player.isPlaying) {
player.reset()
}
if (item.isPlaying) {
item.isPlaying = false
player.reset()
adapter.notifyDataSetChanged()
return
}
this.positionPlayItem = position
adapter.items!!.forEach {
if (it != item) {
it.isPlaying = false
}
}
player.setDataSource(item.path)
player.prepare()
player.start()
item.isPlaying = true
adapter.notifyDataSetChanged()
player.setOnCompletionListener {
player.reset()
adapter.notifyItemChanged(position)
item.isPlaying = false
}
}
private fun stopPlay() {
if (positionPlayItem == -1) {
return
}
player.reset()
adapter.items!![positionPlayItem].isPlaying = false
adapter.notifyItemChanged(positionPlayItem)
}
override fun onMultiSelectVoice(items: ArrayList<VoiceEntity>) {
stopPlay()
if (items.size == 0) {
layoutSelectItem.visibility = View.GONE
return
}
txtCounterSelect.text = String.format(getString(R.string.selected_number), items.size.toString())
setStatusBarColor(R.color.black)
if (layoutSelectItem.visibility == View.GONE) {
layoutSelectItem.visibility = View.VISIBLE
}
if (items.size > 1) {
imgShare.visibility = View.GONE
imgEdit.visibility = View.GONE
} else {
imgShare.visibility = View.VISIBLE
imgEdit.visibility = View.VISIBLE
}
imgCancelSelect.setOnClickListener {
resetData()
}
imgEdit.setOnClickListener {
edit(items.first())
}
imgShare.setOnClickListener {
if (items.isEmpty()) {
return#setOnClickListener
}
shareVoice(this, items[0].path)
}
imgDelete.setOnClickListener {
val alertDialog = AlertDialog.Builder(
supportFragmentManager,
getString(R.string.note), getString(R.string.do_you_sure_delete)
)
alertDialog.setBtnNegative(getString(R.string.no), View.OnClickListener {
alertDialog.dialog!!.dismiss()
})
alertDialog.setBtnPositive(getString(R.string.yes), View.OnClickListener {
val ex = Executors.newSingleThreadExecutor()
items.forEach { item ->
viewModel.remove(item)
ex.execute { File(item.path).deleteOnExit() }
}
items.clear()
layoutSelectItem.visibility = View.GONE
setStatusBarColor(R.color.colorPrimaryDark)
alertDialog.dialog!!.dismissAllowingStateLoss()
})
alertDialog.build().show()
}
}
private fun resetData() {
adapter.itemsSelected.clear()
adapter.items?.forEach { item ->
item.statusSelect = false
}
adapter.notifyDataSetChanged()
layoutSelectItem.visibility = View.GONE
setStatusBarColor(R.color.colorPrimaryDark)
}
private fun edit(item: VoiceEntity) {
val bottomSheet = NameBottomSheet(supportFragmentManager)
bottomSheet.listener = object : NameBottomSheet.OnTitleListener {
override fun onTitle(title: String) {
item.title = title
viewModel.update(item)
resetData()
}
}
bottomSheet.item = item
bottomSheet.show()
}
override fun onBackPressed() {
if (layoutSelectItem.visibility == View.VISIBLE) {
resetData()
return
}
stopPlay()
super.onBackPressed()
}
}
adapter class code :
class VoiceAdapter : AdapterRecyclerView<VoiceEntity>() {
var onItemClickListener: OnClickItemListener? = null
var itemsSelected: ArrayList<VoiceEntity> = ArrayList()
var listenerMultiSelect: OnMultiSelectVoiceListener? = null
override fun getItemLayout(viewType: Int): Int {
return R.layout.item_voice
}
override fun onBindView(
viewDataBinding: ViewDataBinding,
viewHolder: ItemViewHolder,
position: Int,
viewType: Int,
element: VoiceEntity
) {
val binding = viewDataBinding as ItemVoiceBinding
binding.txtTitle.text = element.title
binding.txtDate.text = element.date.toAgoTime(context!!)
binding.icPlay.setImageResource(if (element.isPlaying) R.drawable.ic_pause else R.drawable.ic_play)
binding.seekBar.max = element.duration / 60
val colorSelectItem =
ContextCompat.getColor(binding.rootLayout.context, R.color.color_background_select_item_recycler_view)
val color = if (element.statusSelect) colorSelectItem else Color.TRANSPARENT
binding.rootLayout.setBackgroundColor(color)
if (element.statusSelect) {
changeColorLight(binding)
} else {
changeColorDarker(binding)
}
if (element.isPlaying) {
binding.layoutPlaying.visibility = View.VISIBLE
binding.lottieLayer.playAnimation()
//TODO : change handled voice progressBar show
val t = object : Thread() {
override fun run() {
super.run()
for (i in 0..element.duration) {
Thread.sleep(60)
binding.seekBar.progress = i
if (!element.isPlaying) break
}
}
}
t.start()
} else {
binding.layoutPlaying.visibility = View.GONE
binding.lottieLayer.cancelAnimation()
}
binding.rootLayout.setOnClickListener {
if (itemsSelected.size > 0) {
val item = items!![viewHolder.adapterPosition]
if (itemsSelected.contains(item)) {
item.statusSelect = false
itemsSelected.remove(item)
binding.rootLayout.animatedColorBackgroundSelected(false)
listenerMultiSelect?.onMultiSelectVoice(itemsSelected)
changeColorDarker(binding)
return#setOnClickListener
}
item.statusSelect = true
itemsSelected.add(item)
binding.rootLayout.animatedColorBackgroundSelected()
listenerMultiSelect?.onMultiSelectVoice(itemsSelected)
changeColorLight(binding)
return#setOnClickListener
}
onItemClickListener?.onClickItem(element, position)!!
}
binding.rootLayout.setOnLongClickListener {
val item = items!![viewHolder.adapterPosition]
if (itemsSelected.contains(item)) {
item.statusSelect = false
itemsSelected.remove(item)
binding.rootLayout.animatedColorBackgroundSelected(false)
changeColorDarker(binding)
listenerMultiSelect?.onMultiSelectVoice(itemsSelected)
}
item.statusSelect = true
itemsSelected.add(item)
binding.rootLayout.animatedColorBackgroundSelected()
changeColorLight(binding)
listenerMultiSelect?.onMultiSelectVoice(itemsSelected)
true
}
}
private fun changeColorLight(binding: ItemVoiceBinding) {
binding.txtDate.setTextColor(ContextCompat.getColor(binding.root.context, R.color.color_subtitle_light))
binding.txtTitle.setTextColor(ContextCompat.getColor(binding.root.context, R.color.color_title_light))
}
private fun changeColorDarker(binding: ItemVoiceBinding) {
binding.txtDate.setTextColor(ContextCompat.getColor(binding.root.context, R.color.color_subtitle))
binding.txtTitle.setTextColor(ContextCompat.getColor(binding.root.context, R.color.color_title))
}
interface OnClickItemListener {
fun onClickItem(item: VoiceEntity, position: Int)
}
}
github repository (open source project)

Categories

Resources