I'm tying to parse JSON in recyclerview. App compiles fine but it's outputting empty/blank screen
BlogAdapter.kt
class BlogAdapter(private val blogList: List<Blog>) : RecyclerView.Adapter<BlogAdapter.ViewHolder>() {
override fun getItemCount()= blogList.size
private var mContext: Context? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
this.mContext=parent.context;
return ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.character_item,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val mBlog = this.blogList[position]
if (mBlog.img != null) {
Glide.with(mContext!!)
.load(mBlog.img)
.into(holder.ivThumbnail)
}
if (mBlog.name != null) {
holder.tvTitle.text = mBlog.name
println("Log: Kabe "+mBlog.name)
}
}
class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView){
val ivThumbnail:ImageView = itemView.findViewById(R.id.ivThumbnail);
val tvTitle:TextView = itemView.findViewById(R.id.tvTitle);
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
var mainViewModel: MainViewModel? = null
var mBlogAdapter: BlogAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
getPopularBlog()
swipe_refresh.setOnRefreshListener { getPopularBlog() }
}
private fun getPopularBlog() {
swipe_refresh.isRefreshing = false
mainViewModel!!.allBlog.observe(this, Observer { charactersList ->
prepareRecyclerView(charactersList)
})
}
private fun prepareRecyclerView(blogList: List<Blog>) {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
}
}
My Json file looks like this:
[
{
"id": 1,
"name": "potter",
"img": "https://images.example.com/potter.jpg"
},
{ …}
]
I've created it based on this tutorial: https://itnext.io/kotlin-wrapping-your-head-around-livedata-mutablelivedata-coroutine-networking-and-viewmodel-b552c3a74eec
Any suggestions please
EDIT:
class BlogRepository() {
private var character = mutableListOf<ABCCharacters>()
private var mutableLiveData = MutableLiveData<List<ABCCharacters>>()
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
private val thisApiCorService by lazy {
RestApiService.createCorService()
}
fun getMutableLiveData():MutableLiveData<List<ABCCharacters>> {
coroutineScope.launch {
val request = thisApiCorService.getPopularBlog()
withContext(Dispatchers.Main) {
try {
val response = request.await()
val mBlogWrapper = response;
if (/*mBlogWrapper != null &&*/ mBlogWrapper.isNotEmpty()) {
character = mBlogWrapper as MutableList<ABCCharacters>
mutableLiveData.value = character
}
} catch (e: HttpException) {
// Log exception //
} catch (e: Throwable) {
// Log error //)
}
}
}
return mutableLiveData;
}
}
You forget to call notifyDataSetChanged, when you setup your RecyclerView widget. Below the full method call, to make it works.
private fun prepareRecyclerView(blogList: List<Blog>) {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
mBlogAdapter.notifyDataSetChanged()
}
Try using below implementation:
class MainActivity : AppCompatActivity() {
lateinit var mainViewModel: MainViewModel
var mBlogAdapter: BlogAdapter? = null
var blogList: List<Blog> = arrayListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// init your RV here
prepareRecyclerView()
getPopularBlog()
swipe_refresh.setOnRefreshListener { mainViewModel.getAllBlog() }
}
private fun getPopularBlog() {
swipe_refresh.isRefreshing = false
mainViewModel.getAllBlog().observe(this, Observer { charactersList ->
blogList = charactersList
mBlogAdapter?.notifyDataSetChanged()
})
}
private fun prepareRecyclerView() {
mBlogAdapter = BlogAdapter(blogList)
if (this.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
blogRecyclerView.layoutManager = LinearLayoutManager(this)
} else {
blogRecyclerView.layoutManager = GridLayoutManager(this, 4)
}
blogRecyclerView.itemAnimator = DefaultItemAnimator()
blogRecyclerView.adapter = mBlogAdapter
}
}
Modify your view model like below:
class MainViewModel() : ViewModel() {
val characterRepository= BlogRepository()
fun getAllBlog(): MutableLiveData<List<ABCCharacters>> {
return characterRepository.getMutableLiveData()
}
override fun onCleared() {
super.onCleared()
characterRepository.completableJob.cancel()
}
}
Related
So what I am trying to do here is to fetch data from the api by thecocktaildb.com, but I don't get any data shown.
Adapter:
`
class CocktailAdapter(private val cocktails: List<CocktailDetails>) :
RecyclerView.Adapter<CocktailAdapter.CocktailViewHolder>() {
class CocktailViewHolder(val binding: CocktailListItemBinding) :
RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CocktailViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CocktailListItemBinding.inflate(layoutInflater, parent, false)
return CocktailViewHolder(binding)
}
override fun onBindViewHolder(holder: CocktailViewHolder, position: Int) {
val currentCocktail = cocktails[position]
holder.binding.apply {
cocktail = currentCocktail.strDrink
ivLiked.visibility = if (currentCocktail.favourite) View.VISIBLE else View.GONE
Glide
.with(root.context)
.load(currentCocktail.strDrinkThumb)
.centerCrop()
.placeholder(R.drawable.ic_launcher_foreground)
.into(ivImage)
holder.binding.root.setOnClickListener {
(it.context as MainActivity).supportFragmentManager.commit {
val bundle = Bundle()
bundle.putString("cocktail_name", currentCocktail.strDrink)
replace(R.id.container_view, CocktailDetailsFragment::class.java, bundle)
addToBackStack("cocktails_list_to_details")
}
}
}
}
override fun getItemCount(): Int {
return cocktails.size
}
}
`
Dao:
`
#Dao
interface CocktailDao {
#Query("SELECT * FROM cocktails")
suspend fun getCocktails(): List<CocktailDetails>
#Query("SELECT * FROM cocktails WHERE strDrink=:name")
suspend fun getCocktailByName(name: String): CocktailDetails
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertAll(cocktails: List<CocktailDetails>)
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(cocktail: CocktailDetails)
}
`
Entity:
`
#Entity(tableName = "cocktails")
data class CocktailDetails(
#PrimaryKey
#ColumnInfo(name = "strDrink") var strDrink: String,
#ColumnInfo(name = "strGlass") var strGlass: String,
#ColumnInfo(name = "strDrinkThumb") var strDrinkThumb: String,
// #ColumnInfo(name = "strIngredient") var strIngredient: String,
// #ColumnInfo(name = "strMeasure") var strMeasure: String,
#ColumnInfo(name = "strInstructions") var strInstructions: String,
#ColumnInfo(name = "favourite") var favourite: Boolean
)
`
AppDatabase:
`
package com.aleks.exam.db
import androidx.room.Database
import androidx.room.RoomDatabase
import com.aleks.exam.db.dao.CocktailDao
import com.aleks.exam.db.entity.CocktailDetails
#Database(entities = [CocktailDetails::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun cocktailDao(): CocktailDao
}
`
ViewModelFactory:
`
class CocktailViewModelFactory(
private val cocktailRepository: CocktailRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CocktailViewModel(cocktailRepository) as T
}
}
`
Fragment:
`
class CocktailDetailsFragment : Fragment() {
private lateinit var binding: FragmentCocktailDetailsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val selectedCocktailName = arguments?.getString("cocktail_name", null)
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.getCocktailByName(
selectedCocktailName ?: return#launch
)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCocktailDetailsBinding.inflate(LayoutInflater.from(context))
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeData()
}
private fun observeData() {
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.selectedCocktail?.collect {
binding.cocktail = it ?: return#collect
(activity as? MainActivity)?.runOnUiThread {
setDataBinding()
Glide
.with(context ?: return#runOnUiThread)
.load(it.strDrinkThumb)
.centerCrop()
.placeholder(R.drawable.ic_launcher_foreground)
.into(binding.ivImage)
}
}
}
}
private fun setDataBinding() {
binding.cocktail ?: return
if (binding.cocktail?.favourite == true) {
binding.btnLike.setImageResource(android.R.drawable.star_big_on)
} else {
binding.btnLike.setImageResource(android.R.drawable.star_big_off)
}
binding.btnLike.setOnClickListener {
val cocktail = binding.cocktail
cocktail?.favourite = cocktail?.favourite != true
if (cocktail?.favourite == true) {
binding.btnLike.setImageResource(android.R.drawable.star_big_on)
} else {
binding.btnLike.setImageResource(android.R.drawable.star_big_off)
}
GlobalScope.launch {
(activity as? MainActivity)?.cocktailViewModel?.updateFavourites(
cocktail ?: return#launch
)
}
}
}
}
`
Model:
`
data class CocktailDetailResponse (
var strDrinkThumb: String,
var strDrink: String?,
var strGlass: String?,
// var strIngredient:Ingredient,
// var strMeasure:Measure,
var strInstructions:String?
)
//data class Ingredient (
// var strIngredient1:String,
// var strIngredient2:String,
// var strIngredient3:String,
// var strIngredient4:String,
// var strIngredient5:String,
// var strIngredient6:String,
// var strIngredient7:String,
// var strIngredient8:String,
// var strIngredient9:String,
// var strIngredient10:String,
// var strIngredient11:String,
// var strIngredient12:String,
// var strIngredient13:String,
// var strIngredient14:String,
// var strIngredient15:String
//)
//data class Measure(
// var strMeasure1:String,
// var strMeasure2:String,
// var strMeasure3:String,
// var strMeasure4:String,
// var strMeasure5:String,
// var strMeasure6:String,
// var strMeasure7:String,
// var strMeasure8:String,
// var strMeasure9:String,
// var strMeasure10:String,
// var strMeasure11:String,
// var strMeasure12:String,
// var strMeasure13:String,
// var strMeasure14:String,
// var strMeasure15:String
//)
`
Repository:
`
class CocktailRepository constructor(
private val context: Context,
private val cocktailService: CocktailService,
private val cocktailDao: CocktailDao
) {
suspend fun getCocktails(): List<CocktailDetails> {
return try {
if (NetworkUtil.isConnected(context)) {
val cocktails = cocktailService.getCocktails().execute().body()
val roomCocktails = cocktails?.map { mapResponseToDbModel(it) }
cocktailDao.insertAll(roomCocktails ?: return arrayListOf())
}
cocktailDao.getCocktails()
} catch (e: Exception) {
arrayListOf()
}
}
suspend fun getCocktailByName(name: String): CocktailDetails? {
return try {
if (NetworkUtil.isConnected(context)) {
val cocktails = cocktailService.getCocktailByName(name).execute().body()
val roomCocktails = cocktails?.map { mapResponseToDbModel(it) }
cocktailDao.insertAll(roomCocktails ?: return null)
}
return cocktailDao.getCocktailByName(name)
} catch (e: Exception) {
null
}
}
suspend fun updateCocktail(cocktail: CocktailDetails) {
cocktailDao.update(cocktail)
}
private fun mapResponseToDbModel(response: CocktailDetailResponse): CocktailDetails {
return CocktailDetails(
strDrink = response.strDrink ?: "",
strGlass = response.strGlass ?: "",
strDrinkThumb = response.strDrinkThumb,
// strIngredient = response.strIngredient.strIngredient1,
// strMeasure = response.strMeasure.strMeasure1,
strInstructions = response.strInstructions ?: "",
favourite = false
)
}
}
`
Service:
`
interface CocktailService {
#GET("search.php?f=a")
fun getCocktails(): Call<List<CocktailDetailResponse>>
#GET("search.php?s={strDrink}")
fun getCocktailByName(#Path("strDrink") name: String): Call<List<CocktailDetailResponse>>
}
`
NetworkUtil:
`
object NetworkUtil {
fun isConnected(context: Context): Boolean {
val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities =
connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
if (capabilities != null) {
return if (capabilities.hasTransport(TRANSPORT_CELLULAR)) {
true
} else if (capabilities.hasTransport(TRANSPORT_WIFI)) {
true
} else capabilities.hasTransport(TRANSPORT_ETHERNET)
}
return false
}
}
`
ViewModel:
`
class CocktailViewModel(
private val cocktailRepository: CocktailRepository
) : ViewModel() {
private val cocktailsListStateFlow = MutableStateFlow<List<CocktailDetails>>(arrayListOf())
private val selectedCocktailStateFlow = MutableStateFlow<CocktailDetails?>(null)
val cocktailsList: StateFlow<List<CocktailDetails>> = cocktailsListStateFlow.asStateFlow()
val selectedCocktail: StateFlow<CocktailDetails?> = selectedCocktailStateFlow.asStateFlow()
suspend fun getCocktails() {
val cocktails = cocktailRepository.getCocktails()
cocktailsListStateFlow.value = cocktails
}
suspend fun getCocktailByName(name: String) {
val cocktail = cocktailRepository.getCocktailByName(name)
selectedCocktailStateFlow.value = cocktail ?: return
}
suspend fun updateFavourites(cocktail: CocktailDetails) {
cocktailRepository.updateCocktail(cocktail)
selectedCocktailStateFlow.value =
selectedCocktailStateFlow.value?.copy(favourite = cocktail.favourite)
}
}
`
MainActivity:
`
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var cocktailService: CocktailService
private lateinit var cocktailRepository: CocktailRepository
lateinit var cocktailViewModel: CocktailViewModel
lateinit var db: RoomDatabase
private val retrofit = Retrofit.Builder()
.baseUrl("https://www.thecocktaildb.com/api/json/v1/1/")
.addConverterFactory(GsonConverterFactory.create())
.build()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
init()
observeData()
if (!NetworkUtil.isConnected(this)) {
Snackbar.make(
binding.root,
"No internet connection, information could be outdated",
Snackbar.LENGTH_LONG
).show()
}
GlobalScope.launch {
cocktailViewModel.getCocktails()
}
}
private fun init() {
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"cocktails_database"
).build()
val cocktailDao = (db as AppDatabase).cocktailDao()
cocktailService = retrofit.create(CocktailService::class.java)
cocktailRepository = CocktailRepository(this, cocktailService, cocktailDao)
val cocktailViewModelFactory = CocktailViewModelFactory(cocktailRepository)
cocktailViewModel =
ViewModelProvider(this, cocktailViewModelFactory)[CocktailViewModel::class.java]
}
private fun observeData() {
GlobalScope.launch {
cocktailViewModel.cocktailsList.collect {
runOnUiThread {
val cocktails = it
val sortedCocktails = cocktails.sortedByDescending { it.favourite }
val adapter = CocktailAdapter(sortedCocktails)
binding.cocktailsList.adapter = adapter
binding.tvCocktailsCount.text = "Cocktails: ${it.size}"
}
}
}
}
}
`
I want the Cocktails to be displayed in a list.
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()
}
I am using exoplayer for playing videos .And for this we are used Fragment instance with pagerstateadapter and viewpager2.
But when scroll fast previous played video sound listen in background as well as in screen video means mix the sound in same video player.
Please help me how to solve this.
1.State adapter
class StoriesPagerAdapter(
fragment: Fragment,
val onClick1: VideoItemAdapter.OnItemClicked?,
val onlikeClick: VideoItemAdapter.OnLikeCLicked?,
val onFollowClick: VideoItemAdapter.OnFollowCLicked?,
val ontrendingClick: VideoItemAdapter.OnTrendCLicked?,
val oniconCLick: VideoItemAdapter.OnIconClick?) : FragmentStateAdapter(fragment) {
val dataList:MutableList<Gettrendingvideos.Data.Postlist>=mutableListOf()
override fun getItemCount(): Int {
return dataList.size
}
fun addAll(movies: MutableList<Gettrendingvideos.Data.Postlist>) {
for (movie in movies) {
add(movie)
}
}
fun add(moive: Gettrendingvideos.Data.Postlist) {
dataList.add(moive)
notifyItemInserted(dataList.size - 1)
}
override fun createFragment(position: Int): Fragment {
return StoryViewFragment.newInstance(
onClick1,
onlikeClick,
onFollowClick,
ontrendingClick,
oniconCLick,
dataList[position]
)
}}
2 Fragment
class StoryViewFragment : Fragment(), CommentFragment.onCommentCountIncrease {
private var storyUrl: String? = null
private var storiesDataModel: Gettrendingvideos.Data.Postlist? = null
lateinit var mView: View
private var simplePlayer: SimpleExoPlayer? = null
private var cacheDataSourceFactory: CacheDataSourceFactory? = null
private val simpleCache = MainApplication.simpleCache
private var toPlayVideoPosition: Int = -1
lateinit var viewModel: MainViewModel
lateinit var preferences: SecurePreferences
private var bool: Boolean? = false
var onItemClick: VideoItemAdapter.OnItemClicked? = null
var onlikeCLicked: VideoItemAdapter.OnLikeCLicked? = null
var onFollowCLicked: VideoItemAdapter.OnFollowCLicked? = null
var onTrendCLicked: VideoItemAdapter.OnTrendCLicked? = null
var onIconClick: VideoItemAdapter.OnIconClick? = null
lateinit var huserId: String
lateinit var token: String
companion object {
fun newInstance(
itemClicked: VideoItemAdapter.OnItemClicked?,
likeCLicked: VideoItemAdapter.OnLikeCLicked?,
onFollowCLicked: VideoItemAdapter.OnFollowCLicked?,
onTrendCLicked: VideoItemAdapter.OnTrendCLicked?,
onIconClick: VideoItemAdapter.OnIconClick?,
storiesDataModel: Gettrendingvideos.Data.Postlist
) = StoryViewFragment()
.apply {
arguments = Bundle().apply {
putParcelable(Constants.KEY_STORY_DATA, storiesDataModel)
}
this.onItemClick = itemClicked
this.onlikeCLicked = likeCLicked
this.onFollowCLicked = onFollowCLicked
this.onTrendCLicked = onTrendCLicked
this.onIconClick = onIconClick
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
mView = inflater.inflate(
R.layout.layout_main,
container,
false
)
return mView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(this, ViewModelFactory(RetrofitBuilder.apiService))
.get(MainViewModel::class.java)
preferences =
SecurePreferences(
requireActivity(),
AppConstants.preferenceName,
AppConstants.USER_DETAILS,
true
)
storiesDataModel = arguments?.getParcelable(Constants.KEY_STORY_DATA)
setData()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (!preferences.getString(AppConstants.USER_ID).equals(null)) {
huserId = preferences.getString(AppConstants.USER_ID)!!
Log.d("TAG", "onActivityCreated: $huserId")
}
}
#SuppressLint("SetTextI18n")
private fun setData() {
Log.d("TAG", "setData: $storiesDataModel")
mView.textview2.text = storiesDataModel?.user_name
mView.like_count.text = storiesDataModel?.total_likes.toString()
comment_count.text = storiesDataModel?.total_comments.toString()
mView.textview.text = storiesDataModel?.type
Glide.with(this).load(storiesDataModel?.user_profile_pic).placeholder(R.drawable.profile).into(mView.image)
if (storiesDataModel?.is_like == 0) {
mView.imageView4.setImageResource(R.drawable.ic_like)
} else {
mView.imageView4.setImageResource(R.drawable.ic_like_red)
}
if (storiesDataModel?.is_following!! == 0) {
mView.textview3.text = "Follow"
} else {
mView.textview3.text = "Following"
}
if (storiesDataModel?.user_id.toString()==preferences.getString(AppConstants.USER_ID)) {
mView.textview3.visibility = View.GONE
}
image.setOnClickListener {
if (preferences.getString(AppConstants.token).equals(null)) {
MainActivity().show(childFragmentManager, "")
} else {
preferences.put(
AppConstants.OtherProfile_UserId,
storiesDataModel?.user_id.toString()
)
}
}
val simplePlayer = getPlayer()
player_view_story.player = simplePlayer player_view_story.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_FILL)
simplePlayer?.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING)
storyUrl = storiesDataModel?.video_url
prepareMedia(storiesDataModel)
}
override fun onPause() {
pauseVideo()
super.onPause()
}
override fun onResume() {
restartVideo()
super.onResume()
}
override fun onDestroy() {
releasePlayer()
super.onDestroy()
}
private fun pausePlayer() {
simplePlayer?.setPlayWhenReady(false)
simplePlayer?.getPlaybackState()
}
private fun startPlayer() {
simplePlayer?.setPlayWhenReady(true)
simplePlayer?.getPlaybackState()
}
private val playerCallback: Player.EventListener? = object : Player.EventListener {
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
}
override fun onPlayerError(error: com.google.android.exoplayer2.ExoPlaybackException?) {
super.onPlayerError(error)
}
}
private fun prepareVideoPlayer() {
simplePlayer = ExoPlayerFactory.newSimpleInstance(context)
cacheDataSourceFactory = CacheDataSourceFactory(
simpleCache,
DefaultHttpDataSourceFactory(
Util.getUserAgent(
context,
"exo"
)
)
)
}
private fun getPlayer(): SimpleExoPlayer? {
if (simplePlayer == null) {
prepareVideoPlayer()
}
return simplePlayer
}
private fun prepareMedia(datamodel: Gettrendingvideos.Data.Postlist?{
val uri = Uri.parse(datamodel?.video_url)
simplePlayer.repeatMode = Player.REPEAT_MODE_ONE
simplePlayer.playWhenReady = true
if (storiesDataModel!!.type == "following") {
following_page.typeface = Typeface.DEFAULT_BOLD
trending_page.setTypeface(null, Typeface.NORMAL)
} else {
following_page.setTypeface(null, Typeface.BOLD)
}
if (storiesDataModel.type == "treading") {
trending_page.typeface = Typeface.DEFAULT_BOLD
following_page.setTypeface(null, Typeface.NORMAL)
} else {
trending_page.setTypeface(null, Typeface.BOLD)
}
if (simplePlayer.playWhenReady == true) {
}
simplePlayer.addListener(playerCallback)
toPlayVideoPosition = -1
}
private fun setArtwork(drawable: Drawable, playerView: PlayerView) {
playerView.useArtwork = true
playerView.defaultArtwork = drawable
}
private fun playVideo() {
simplePlayer.playWhenReady = true
}
private fun restartVideo() {
if (simplePlayer == null) {
prepareMedia(storiesDataModel)
} else {
simplePlayer.seekToDefaultPosition()
simplePlayer.playWhenReady = true
}
}
private fun pauseVideo() {
simplePlayer.playWhenReady = false
}
private fun releasePlayer() {
simplePlayer.stop(true)
simplePlayer.release()
}}
override fun setMenuVisibility(menuVisible: Boolean) {
if (!menuVisible){
simplePlayer?.playWhenReady = false
simplePlayer?.pause()
}
super.setMenuVisibility(menuVisible)
}
JUST ADD THIS IN YOUR StoryViewFragment.
I have two screens first one has recycler view list of data and searchView above it that's filter data in this recycler, the view Model code of the first fragment
class MscInspectionViewModel(val activity: LaunchActivity, val mRootView: MscInspectFragment) :
BaseViewModel(),
SwipeRefreshLayout.OnRefreshListener {
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
private val getDataError = MutableLiveData<Boolean>()
var listType = MutableLiveData<Int>()
val hint = MutableLiveData<String>()
private var isRefreshing: Boolean = false
private var mSharedPreferences: SharedPreferences? = null
val dataListAdapter = ContainersUnderCheckAdapter(activity)
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val filterDataByTab = object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabSelected(tab: TabLayout.Tab?) {
when (tab!!.text) {
activity.resources.getString(R.string.cidPending) -> {
listType.value = 0
getPendingData()
}
activity.resources.getString(R.string.cidDone) -> {
listType.value = 1
getDoneData()
}
}
}
}
val filterData = object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
if (query.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(query)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
return true
}
override fun onQueryTextChange(newText: String): Boolean {
if (newText.length > 2) {
val mQuery = Utility(activity).switchArabicNumerals(newText)
dataListAdapter.getFilter(3, listType.value!!).filter(mQuery)
}
return false;
}
}
val closeImgListener = View.OnClickListener {
mRootView.svSearchMSC.setQuery("", true)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
init {
listType.value = 0
mSharedPreferences = getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = activity.resources.getString(R.string.mscInspectTitle)
hint.value = activity.resources.getString(R.string.msc_search)
getData()
}
fun getData() {
onRetrievePostListStart()
subscription = apiAccount.getContainersUnderCheck(
"getContainersUnderCheck",
mSharedPreferences!!.getString(Constants.CFID, "")!!,
mSharedPreferences!!.getString(Constants.CFTOKEN, "")!!
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe {}
.doOnTerminate {}
.subscribe({ result ->
result?.let {
if (result.ResponseCode != null && result.ResponseCode.trim() != "000") {
onRetrievePostListError(result.ResponseMessage)
} else {
result.ContainersData?.let { it1 -> onRetrievePostListSuccess(it1) }
}
}
}, { throwable ->
android.util.Log.e("getDataInquiry", throwable.message!!)
onRetrievePostListError(activity.resources.getString(R.string.general_error))
})
}
private fun getPendingData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun getDoneData() {
val query = mRootView.svSearchMSC.query.toString()
if (query == "") {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else {
if (query.length > 2) {
dataListAdapter.getFilter(3, listType.value!!).filter(query)
} else {
errorMessage.value = activity.resources.getString(R.string.addCorrectNumber)
}
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
isRefreshing = false
}
private fun onRetrievePostListSuccess(containersData: List<ContainersData>) {
onRetrievePostListFinish()
dataListAdapter.updateInquiryAdapter(containersData as ArrayList<ContainersData>)
if (listType.value == 1) {
dataListAdapter.getFilter(1, listType.value!!).filter("ANY")
} else if (listType.value == 0) {
dataListAdapter.getFilter(2, listType.value!!).filter("PENDING")
}
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
override fun onCleared() {
super.onCleared()
subscription.dispose()
}
override fun onRefresh() {
isRefreshing = true
getData()
}
}
adapter is :
class ContainersUnderCheckAdapter(val activity: LaunchActivity) :
RecyclerView.Adapter<ContainersUnderCheckAdapter.ViewHolder>() {
private lateinit var mDataSet: ArrayList<ContainersData>
private lateinit var mDataSetFiltered: ArrayList<ContainersData>
fun updateInquiryAdapter(dataSet: ArrayList<ContainersData>) {
mDataSet = ArrayList()
mDataSet.clear()
mDataSet.addAll(dataSet)
mDataSetFiltered = mDataSet
getFilter(2, 1).filter("PENDING")
// notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerItemFieldLayoutBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_item_field_layout,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return if (::mDataSetFiltered.isInitialized) mDataSetFiltered.size else 0
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(mDataSetFiltered[position])
}
operator fun get(position: Int): ContainersData {
return mDataSetFiltered.get(position)
}
/**
* #filterType :
* IF 1 : filter on Data Type RJCTD + APPROVED
* 2 : filter on Data Type PENDING
* 3 :
*/
fun getFilter(filterType: Int, listType: Int): Filter {
return object : Filter() {
override fun performFiltering(charSequence: CharSequence): FilterResults {
val charString = charSequence.toString()
mDataSetFiltered = if (charString.isEmpty()) {
mDataSet
} else {
val filteredList = ArrayList<ContainersData>()
for (row in mDataSet) {
when (filterType) {
1 -> {
if (row.status == "RJCTD" || row.status == "APPROVED") {
filteredList.add(row)
}
}
2 -> {
if (row.status == charString) {
filteredList.add(row)
}
}
3 -> {
when (listType) {
0 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status == "PENDING"
) {
filteredList.add(row)
}
}
1 -> {
if ((row.CID!!.contains(charString.toUpperCase(Locale.ROOT)) || row.TN!!.contains(
charSequence
) || row.PN!!.contains(charSequence)) && row.status != "PENDING"
) {
filteredList.add(row)
}
}
}
}
}
}
filteredList
}
val filterResults = FilterResults()
filterResults.values = mDataSetFiltered
return filterResults
}
override fun publishResults(
charSequence: CharSequence,
filterResults: FilterResults
) {
if (::mDataSetFiltered.isInitialized) {
mDataSetFiltered = try {
filterResults.values as ArrayList<ContainersData>
} catch (e: Exception) {
Log.e("mDataSetFiltered",e.message!!)
ArrayList()
}
when (filterType) {
1->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p1!!.UpdateDate.compareTo(p0!!.UpdateDate) })
}
2->{
mDataSetFiltered.sortWith(Comparator { p0, p1 -> p0!!.ID!!.compareTo(p1.ID!!) })
}
}
}
// refresh the list with filtered data
notifyDataSetChanged()
}
}
}
class ViewHolder(
private val binding: ContianerItemFieldLayoutBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscInspectionListViewModel(activity)
fun bind(data: ContainersData) {
viewModel.bind(data)
binding.viewModel = viewModel
}
}
}
any data in this recycler on click go to fragment has tow recycler first one to show data, the second one to pick Images
the second-page code
class MSCDataFragment : Fragment() {
lateinit var rootView: View
lateinit var activity: LaunchActivity
lateinit var utility: Utility
lateinit var loadingView: LoadingView
private lateinit var viewModel: MSCDataViewModel
private lateinit var binding: FragmentMscdataBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (getActivity() != null) {
activity = getActivity() as LaunchActivity
utility = Utility(activity)
loadingView = LoadingView(activity)
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mscdata, container, false)
rootView = binding.root
initial()
return rootView
}
private fun initial() {
viewModel = ViewModelProvider(
this, ViewModelFactory(
activity,
arguments!!.getSerializable("Data") as ContainersData
)
).get(MSCDataViewModel::class.java)
binding.viewModel = viewModel
// binding.imgList.layoutManager = GridLayoutManager(activity, 3)
binding.containerInfo.layoutManager = LinearLayoutManager(activity)
binding.openCIDNotValid.typeface =
Typeface.createFromAsset(activity.assets, "Bahij_Janna-Regular.ttf")
binding.openCIDNotValid.setOnCheckedChangeListener(viewModel.onOpenCidNotValidListener)
viewModel.loading.observe(this, Observer { loading ->
loading?.let {
if (it) {
loadingView.show()
} else {
loadingView.dismiss()
}
}
})
viewModel.errorMessage.observe(this, Observer { msg ->
msg?.let {
utility.ShowToast(msg)
}
})
viewModel.imagesAdapters2.observe(this, Observer { msg ->
msg?.let {
binding.imgList.apply {
layoutManager = GridLayoutManager(activity, 3)
adapter = it
}
}
})
rootView.toolbar_Back.setOnClickListener(viewModel.backClickListener)
binding.btnAddImages.setOnClickListener(viewModel.pickImages)
binding.successContianer.setOnClickListener(viewModel.correctContainer)
binding.damagedContianer.setOnClickListener(viewModel.wrongContainer)
}
}
the view model is :
class MSCDataViewModel(val activity: LaunchActivity, val containersData: ContainersData) :
BaseViewModel(), GetImagesListener {
#Inject
lateinit var restApiAccount: RestApiAccount
val toolBarTitle: MutableLiveData<String> = MutableLiveData()
val ButtonText: MutableLiveData<String> = MutableLiveData()
var openCIDNotValidVisibility = MutableLiveData<Int>()
private val getDataError = MutableLiveData<Boolean>()
val btnImagesVisibility = MutableLiveData<Int>()
var imgeNoteVisibility = MutableLiveData<Int>()
var successVisibility = MutableLiveData<Int>()
var damagedVisibility = MutableLiveData<Int>()
var notesVisibility = MutableLiveData<Int>()
val btnVisibility = MutableLiveData<Int>()
var canNotOpen = MutableLiveData<Int>()
private val images = ArrayList<Image>()
var utility = Utility(activity)
private var CURRENTINDEX = 0
private var mSharedPreferences: SharedPreferences? = null
val DataListAdapter = ContainerDataAdapter(activity)
var imagesAdapter = ContainerImagesAdapter(activity, containersData.status!!, ArrayList())
val imagesAdapters2 = MutableLiveData<ContainerImagesAdapter2>()
val userInfo: UserInfo
val backClickListener = View.OnClickListener { activity.supportFragmentManager.popBackStack() }
val pickImages = View.OnClickListener {
pickImages()
}
val correctContainer = View.OnClickListener {}
val onOpenCidNotValidListener =
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
if (isChecked) {
successVisibility.value = View.GONE
canNotOpen.value = 1
} else {
canNotOpen.value = 0
successVisibility.value = View.VISIBLE
}
}
val wrongContainer = View.OnClickListener {}
var mscNotes: ObservableField<String> = ObservableField("")
init {
canNotOpen.value = 0
mSharedPreferences =
PreferenceManager.getDefaultSharedPreferences(activity.applicationContext)
toolBarTitle.value = containersData.CID
ButtonText.value = activity.resources.getString(R.string.cleanContianer)
userInfo = utility.readObjectFromSharedPreferences(
mSharedPreferences,
Constants.USER_INFO_KEY,
UserInfo::class.java
) as UserInfo
openCIDNotValidVisibility.value = View.GONE
fillData()
}
private fun fillData() {
val data: LinkedHashMap<String, String> = containersData.data!!
val captionsMap = utility.readObjectFromSharedPreferences(
mSharedPreferences, Constants.CAPTIONS_MAP_KEY,
HashMap::class.java
) as HashMap<String, String>
if (containersData.data.size > 0) {
val list = ArrayList<KeyValueModel>()
for (inside in data.keys) {
val ky = captionsMap[inside]
val value = data[inside].toString()
ky?.let { KeyValueModel(it, value) }?.let { list.add(it) }
}
DataListAdapter.updateInquiryAdapter(list)
} else {
errorMessage.value = activity.resources.getString(R.string.no_data)
}
if (containersData.ImageList != null && containersData.ImageList.isNotEmpty()) {
imagesAdapter.updateContainerImagesAdapter(containersData.ImageList)
}
}
private fun pickImages() {
activity.setCallBack(this)
val pictureDialog: AlertDialog
val builder = activity.let { AlertDialog.Builder(it) }
val dialogView = View.inflate(activity, R.layout.choose_camera_method, null)
builder.setView(dialogView)
val nafithPopupContainer = dialogView.findViewById<RelativeLayout>(R.id.RLTitle)
nafithPopupContainer.setBackgroundColor(
ContextCompat.getColor(
activity,
R.color.mainColor
)
)
val popUpGallery = dialogView.findViewById<LinearLayout>(R.id.PopupGellary)
val popUpCamera = dialogView.findViewById<LinearLayout>(R.id.PopupCamera)
pictureDialog = builder.create()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
Objects.requireNonNull<Window>(pictureDialog.window)
.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
} else {
if (pictureDialog.window != null) {
pictureDialog.window!!.setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
}
popUpGallery.setOnClickListener {
fromGallery()
pictureDialog.dismiss()
}
popUpCamera.setOnClickListener {
fromCamera()
pictureDialog.dismiss()
}
val popupClose = dialogView.findViewById<ImageView>(R.id.popupClose)
popupClose.setOnClickListener { pictureDialog.dismiss() }
pictureDialog.show()
}
private fun fromGallery() {
ImagePicker.create(activity)
.toolbarImageTitle(activity.resources.getString(R.string.get_image))
.toolbarArrowColor(ContextCompat.getColor(activity, R.color.colorWhite))
.showCamera(false)
.limit(6)
.start()
}
private fun fromCamera() {
ImagePicker.cameraOnly().start(activity)
}
override fun onGetImage(image: Image) {
imgeNoteVisibility.value = View.GONE
imagesAdapter.updateContainerImagesAdapter(image)
images.add(image)
}
override fun addingImagesDone(mImages: MutableList<Image>) {
images.clear()
images.addAll(mImages)
imgeNoteVisibility.value = View.GONE
val listString :ArrayList<String> = ArrayList()
for (i in mImages.indices){
listString.add(mImages[i].path)
}
imagesAdapters2.value = ContainerImagesAdapter2(activity,containersData.status!!,listString)
imagesAdapters2.value!!.notifyItemRangeChanged(0,listString.size)
}
override fun onImgDelete(image: String) {
var x = 0
try {
for (i in 0 until images.size) {
x = i
if (images[i].path == image) {
images.remove(images[i])
}
}
} catch (e: Exception) {
Log.e("errorImages", e.message!!)
Log.e("xx", x.toString())
}
}
private fun onRetrievePostListStart() {
loading.value = true
}
private fun onRetrievePostListFinish() {
loading.value = false
}
private fun onRetrievePostListSuccess(msg: String?) {
onRetrievePostListFinish()
}
private fun onRetrievePostListError(message: String?) {
onRetrievePostListFinish()
getDataError.value = true
errorMessage.value = message
}
}
Adapter code is :
class ContainerImagesAdapter2() : RecyclerView.Adapter<ContainerImagesAdapter2.ViewHolder>() {
var status: String = ""
lateinit var activity: LaunchActivity
lateinit var utility: Utility
constructor(
mActivity: LaunchActivity,
mStatus: String,
pathsList: ArrayList<String>
) : this() {
activity = mActivity
pathsDataSet = pathsList
status = mStatus
utility = Utility(activity)
}
private var pathsDataSet: ArrayList<String> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ContianerImageFieldBinding = DataBindingUtil
.inflate(
LayoutInflater.from(parent.context),
R.layout.contianer_image_field,
parent,
false
)
return ViewHolder(binding, activity)
}
override fun getItemCount(): Int {
return pathsDataSet.size
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindPath(pathsDataSet[position], position)
}
inner class ViewHolder(
private val binding: ContianerImageFieldBinding,
val activity: LaunchActivity
) : RecyclerView.ViewHolder(binding.root) {
private val viewModel = MscImagesListViewModel(activity)
fun bindPath(data: String, position: Int) {
viewModel.bindPath(data)
binding.viewModel = viewModel
if (status != "PENDING") {
binding.closeImg.visibility = View.GONE
}
binding.closeImg.setOnClickListener {}
binding.mainImg.setOnClickListener {
val fragment = FullImageFragment()
val bundle = Bundle()
val list = ArrayList<String>()
for (item in 0 until pathsDataSet.size) {
list.add(pathsDataSet[item])
}
bundle.putSerializable("ImageList", list)
bundle.putInt("Position", position)
fragment.arguments = bundle
activity.supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment).addToBackStack(fragment.tag)
.commit()
}
}
}
}
if you filter data using search view in the first-page and pick images in the second page , list of picked images doesn't appear, if you going to the second page without filtering data everything ok
solve Problem found
Just Update constraint-layout library in gradle dependencies to version '2.0.0-beta4'
I have backend firebase which i am using to populate the listview , i was implementing the search functionality suddenly i found a weird behavior in listview ,it is creating duplicates in numbers like 30-40 and above.
Please help me i was batteling for it from 3 days. i am very new to development so any suggestions to code optimizing will also be taken as help!
class Menu : AppCompatActivity(){
private var prevKey : String? = null
var listAdapter : CustomAdapter ? = null
private val listItemArrayList = ArrayList<ListItem>()
var rootRef : FirebaseDatabase? = null
var adminRef : DatabaseReference? = null
var mAuth : FirebaseAuth? = null
var helper : CustomAdapter?=null
private var user : String?=null
var mAuthListener: FirebaseAuth.AuthStateListener? = null
var Items = ArrayList<String>()
var isSectionBoolean = ArrayList<Boolean>()
var ItemCategory= ArrayList<String>()
var ItemCost = ArrayList<Int>()
var listView : ListView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_menu)
mAuth = FirebaseAuth.getInstance()
mAuthListener = FirebaseAuth.AuthStateListener { }
rootRef = FirebaseDatabase.getInstance()
user =mAuth!!.currentUser!!.phoneNumber as String
adminRef = rootRef!!.getReference("AdminCafe").child(user!!)
listView = findViewById(R.id.admindynamiclist)
listAdapter = CustomAdapter(listItemArrayList,this)
listView!!.adapter = listAdapter
listView!!.isTextFilterEnabled = true
SearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener{
override fun onQueryTextSubmit(query: String?): Boolean {
return false
}
override fun onQueryTextChange(newText: String?): Boolean {
listAdapter!!.filter(newText.toString())
return false
}
})
getData()
}
fun addAct(v:View)
{
val intent = Intent(applicationContext,AddActivity::class.java)
startActivity(intent)
}
private fun getData() {
println("getData")
var menucatRef = adminRef!!.child("MenuItem")
menucatRef.addValueEventListener(object : ValueEventListener{
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
for(Snap in p0.children)
{
println("wtfff")
println("Child"+Snap.childrenCount)
getDataFromKey(Snap.key.toString(),Snap.childrenCount.toInt())
}
}
})
}
private fun getDataFromKey(key: String, child :Int) {
listItemArrayList.clear()
println("called")
var count = 0
var counter = 0
var Category = CategoryHeader()
var menuRef = adminRef!!.child("MenuItem").child(key)
menuRef.addListenerForSingleValueEvent(object : ValueEventListener{
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(p0: DataSnapshot) {
var catKey = p0.key.toString()
for(snap in p0!!.children)
{
if(count == 0)
{
Category!!.setheader(catKey)
listItemArrayList!!.add(Category)
println("Seperator will be added")
}
else
{
var hashMap = snap.value as HashMap<String,String>
var itemName = snap.key
var isVeg = hashMap["isVeg"]
var itemCost = hashMap["itemCost"]!!.toInt()
var isSig = hashMap["isSig"]
var ItemHelper = Item()
if(itemName!=null)
{
ItemHelper.setItemName(itemName)
}
if(itemCost!=null)
{
ItemHelper.setItemCost(itemCost)
}
listItemArrayList!!.add(ItemHelper)
}
count++
}
listAdapter!!.notifyDataSetChanged()
}
})
}
interface ListItem {
fun isHeader(): Boolean
fun getName(): String
fun getItemCost() : Int
}
fun editAct(v:View)
{
println("Inside edit")
}
}
And Adapter class
class CustomAdapter(private var listItem : ArrayList<Menu.ListItem>, private val context: Activity) : BaseAdapter() {
var orig = ArrayList<Menu.ListItem> ()
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
orig.addAll(listItem)
println("List Item is"+listItem)
println("List Item is"+orig)
var holder = ViewHolder()
var customView = convertView
var flag = true
if (convertView == null) {
val layoutInflater = context.layoutInflater
if(listItem.get(position).isHeader()) {
println("position"+position)
customView = layoutInflater.inflate(R.layout.seperator, null)
holder.tvLabel = customView.categoryText
} else {
println("position"+position)
customView = layoutInflater.inflate(R.layout.list_item, null)
holder.tvLabel = customView.itemad
holder.itemCost = customView.cost
flag = false
}
customView.setTag(holder)
}
else
{
customView = convertView
holder = convertView!!.tag as ViewHolder
}
if(flag) {
println("Custoom Flag is"+flag)
holder!!.tvLabel!!.text = listItem.get(position).getName()
}
else {
println("Custoom Flag is"+flag)
holder!!.tvLabel!!.text = listItem.get(position).getName()
holder.itemCost!!.text = listItem.get(position).getItemCost().toString()
}
return customView!!
}
override fun getItem(position: Int): Any {
return listItem.get(position)
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
return listItem.size
}
private inner class ViewHolder {
var tvLabel: TextView? = null
var itemCost: TextView? = null
}
fun filter(query : String)
{
println("called")
listItem.clear()
var search = query
search = search.toLowerCase(Locale.getDefault())
if(search.isEmpty())
{
listItem.addAll(orig!!)
println("Inside size 0")
}
else
{
var counter = 0
for(result in orig!! )
{
if(result.getName().toLowerCase().contains(search))
{
println("Result is "+result.getName())
println("listItem"+listItem)
}
}
}
}
}