I am trying to change my radiobutton.buttonDrawable inside my adapter. Selecting static drawables from my drawable folder works. What I now want is to download the drawables (.svg files) from my cloud-firestore-storage, convert them to a drawable and set them to the radiobutton.
My current implementation doesn't work as it just does nothing (doesn't change anything). I've already checked the urls (getItem(position).icon) and they seem correct.
Current approach
class ShopFilterItemAdapter #Inject constructor(
// #ApplicationContext private val context: Context
) : ListAdapter<ShopFilterCategory, ShopFilterItemAdapter.ShopFilterViewHolder>(Companion) {
private var checkedRadioButton: CompoundButton? = null
var defaultCheckedId: Int = 0
private lateinit var listener: OnItemSelectedListener
companion object: DiffUtil.ItemCallback<ShopFilterCategory>() {
override fun areItemsTheSame(oldItem: ShopFilterCategory, newItem: ShopFilterCategory): Boolean = oldItem === newItem
override fun areContentsTheSame(oldItem: ShopFilterCategory, newItem: ShopFilterCategory): Boolean = oldItem == newItem
}
inner class ShopFilterViewHolder(val binding: ShopFilterListItemBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ShopFilterViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ShopFilterListItemBinding.inflate(layoutInflater, parent, false)
return ShopFilterViewHolder(binding)
}
override fun onBindViewHolder(holder: ShopFilterViewHolder, position: Int) {
holder.binding.filterItem = getItem(position)
if (checkedRadioButton == null && defaultCheckedId == position) holder.binding.rbItem.isChecked = true
if (holder.binding.rbItem.isChecked) checkedRadioButton = holder.binding.rbItem
// Trying to load the images here
Glide.with(holder.binding.root)
.asDrawable()
.load(getItem(position).icon)
.into(object : CustomTarget<Drawable>() {
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
holder.binding.rbItem.buttonDrawable = resource
}
override fun onLoadCleared(placeholder: Drawable?) {
TODO("Not yet implemented")
}
})
holder.binding.executePendingBindings()
}
EDIT
I was partially wrong, it throws an error:
java.io.IOException: java.lang.RuntimeException: setDataSourceCallback failed: status = 0x80000000
at com.bumptech.glide.load.resource.bitmap.VideoDecoder.decode(VideoDecoder.java:185)
at com.bumptech.glide.load.engine.DecodePath.decodeResourceWithList(DecodePath.java:92)
at com.bumptech.glide.load.engine.DecodePath.decodeResource(DecodePath.java:70)
at com.bumptech.glide.load.engine.DecodePath.decode(DecodePath.java:59)
at com.bumptech.glide.load.engine.LoadPath.loadWithExceptionList(LoadPath.java:76)
at com.bumptech.glide.load.engine.LoadPath.load(LoadPath.java:57)
at com.bumptech.glide.load.engine.DecodeJob.runLoadPath(DecodeJob.java:524)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromFetcher(DecodeJob.java:488)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromData(DecodeJob.java:474)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromRetrievedData(DecodeJob.java:426)
at com.bumptech.glide.load.engine.DecodeJob.onDataFetcherReady(DecodeJob.java:390)
at com.bumptech.glide.load.engine.SourceGenerator.onDataFetcherReady(SourceGenerator.java:176)
at com.bumptech.glide.load.engine.DataCacheGenerator.onDataReady(DataCacheGenerator.java:94)
at com.bumptech.glide.load.model.ByteBufferFileLoader$ByteBufferFetcher.loadData(ByteBufferFileLoader.java:70)
at com.bumptech.glide.load.engine.DataCacheGenerator.startNext(DataCacheGenerator.java:74)
at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:50)
at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:310)
at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:279)
at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:234)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:393)
Caused by: java.lang.RuntimeException: setDataSourceCallback failed: status = 0x80000000
at android.media.MediaMetadataRetriever._setDataSource(Native Method)
at android.media.MediaMetadataRetriever.setDataSource(MediaMetadataRetriever.java:210)
at com.bumptech.glide.load.resource.bitmap.VideoDecoder$ByteBufferInitializer.initialize(VideoDecoder.java:316)
at com.bumptech.glide.load.resource.bitmap.VideoDecoder$ByteBufferInitializer.initialize(VideoDecoder.java:310)
at com.bumptech.glide.load.resource.bitmap.VideoDecoder.decode(VideoDecoder.java:173)
at com.bumptech.glide.load.engine.DecodePath.decodeResourceWithList(DecodePath.java:92)
at com.bumptech.glide.load.engine.DecodePath.decodeResource(DecodePath.java:70)
at com.bumptech.glide.load.engine.DecodePath.decode(DecodePath.java:59)
at com.bumptech.glide.load.engine.LoadPath.loadWithExceptionList(LoadPath.java:76)
at com.bumptech.glide.load.engine.LoadPath.load(LoadPath.java:57)
at com.bumptech.glide.load.engine.DecodeJob.runLoadPath(DecodeJob.java:524)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromFetcher(DecodeJob.java:488)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromData(DecodeJob.java:474)
at com.bumptech.glide.load.engine.DecodeJob.decodeFromRetrievedData(DecodeJob.java:426)
at com.bumptech.glide.load.engine.DecodeJob.onDataFetcherReady(DecodeJob.java:390)
at com.bumptech.glide.load.engine.SourceGenerator.onDataFetcherReady(SourceGenerator.java:176)
at com.bumptech.glide.load.engine.DataCacheGenerator.onDataReady(DataCacheGenerator.java:94)
at com.bumptech.glide.load.model.ByteBufferFileLoader$ByteBufferFetcher.loadData(ByteBufferFileLoader.java:70)
at com.bumptech.glide.load.engine.DataCacheGenerator.startNext(DataCacheGenerator.java:74)
at com.bumptech.glide.load.engine.SourceGenerator.startNext(SourceGenerator.java:50)
at com.bumptech.glide.load.engine.DecodeJob.runGenerators(DecodeJob.java:310)
at com.bumptech.glide.load.engine.DecodeJob.runWrapped(DecodeJob.java:279)
at com.bumptech.glide.load.engine.DecodeJob.run(DecodeJob.java:234)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
at com.bumptech.glide.load.engine.executor.GlideExecutor$DefaultThreadFactory$1.run(GlideExecutor.java:393)
I've managed to solve this problem, unfortunately it is not possible with glide, but with coil.
1. Add the necessary dependencies
implementation "io.coil-kt:coil-base:1.1.0" // or "io.coil-kt:coil-1.1.0" if you don't want to use dependency Injection
implementation "io.coil-kt:coil-svg:1.1.0"
2. Construct the ImageLoader Singleton and add the SVGEndoder
#Provides
#Singleton
fun provideImageLoader(#ApplicationContext context: Context) = ImageLoader.Builder(context)
.componentRegistry { add(SvgDecoder(context)) }
.build()
3.Build the request
fun buildRequest(context: Context, data: Any, target: RadioButton) = ImageRequest.Builder(context)
.data(data)
.target { drawable -> target.buttonDrawable = drawable }
.size(64, 60) // unfortunately I was not able to get the default radiobutton.drawable size here
.build()
4. Inside the Adapter
override fun onBindViewHolder(holder: YourHolder, position: Int) {
val newRequest = shopFilterValidator.buildRequest(
context = holder.binding.rbItem.context,
data = getItem(position).icon, // here a url from firebase
target = holder.binding.rbItem
)
imageLoader.enqueue(newRequest)
}
This will load the svg images as drawables and put them as the radiobutton.drawable
Related
I am trying to make an api call with paging 3 and retrofit. But for some reason the api call is not getting triggered. I checked my code multiple times to find an issue but couldn't understand why am I doing wrong. When I make that api call seperately then its getting called. But not with paging 3. Can someone give some idea why it might be the case? And how can I find the problem here ?
My Api Service Class
interface ApiService {
companion object{
const val BASE_URL = "https://picsum.photos/v2/"
}
#GET("list")
suspend fun getImageList(
#Query("page") pageNo:Int,
#Query("limit") limit: Int=20
):Response<ImageListResponse>
}
My Paging Source
class ImagePagingSource(private val api:ApiService): PagingSource<Int, ImageItem>(){
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, ImageItem> {
try {
Log.d("ImagePagingSource","Load is Called")
val position = params.key ?: 1
val previousKey = if (position == 1) null else position - 1
/*
* We don't have the total page key in this api response.
* So restricting the call after page 10 statically.
* */
val nextKey = if (position == 10) null else position + 1
val response = api.getImageList(position)
if (!response.isSuccessful) return LoadResult.Error(Exception("Api Error : ${response.message()}"))
if (response.body().isNullOrEmpty()) return LoadResult.Error(Exception("No Image Found"))
Log.d("ImagePagingSource",response.body().toString())
return LoadResult.Page(
data = response.body()!!,
prevKey = previousKey,
nextKey = nextKey
)
} catch (e: Exception) {
return LoadResult.Error(e)
}
}
override fun getRefreshKey(state: PagingState<Int, ImageItem>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
}
}
}
My Repository Class
class HomePageRepo
#Inject
constructor(
private val apiService: ApiService
)
{
fun getImages() = Pager(
config = PagingConfig(pageSize = 20, maxSize = 100),
pagingSourceFactory = { ImagePagingSource(apiService) }
).liveData
suspend fun callApi(){
apiService.getImageList(1)
}
}
View Model
fun getSearchResult():LiveData<PagingData<ImageItem>> = repo.getImages().cachedIn(viewModelScope)
Paging Adapter For Recycler View
class ImageShowPagingAdapter(private val context:Context) : PagingDataAdapter<
ImageItem,
ImageShowPagingAdapter.ImageShowViewHolder
>(COMPARATOR) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageShowViewHolder {
val inflater = LayoutInflater.from(context)
val view = RecViewItemImagesBinding.inflate(inflater, parent, false)
return ImageShowViewHolder(view)
}
override fun onBindViewHolder(holder: ImageShowViewHolder, position: Int) {
val item = getItem(position)
Glide.with(context)
.load(item?.download_url)
.apply(
RequestOptions()
.placeholder(R.drawable.all_type_content_default)
.error(R.drawable.all_type_content_default)
)
.into(holder.binding.ivImage)
}
inner class ImageShowViewHolder(val binding: RecViewItemImagesBinding) : RecyclerView.ViewHolder(binding.root)
companion object {
private val COMPARATOR = object : DiffUtil.ItemCallback<ImageItem>() {
override fun areItemsTheSame(oldItem: ImageItem, newItem: ImageItem): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ImageItem, newItem: ImageItem): Boolean {
return oldItem == newItem
}
}
}
}
Here is the complete url that I am trying to hit
https://picsum.photos/v2/list?page=1&limit=20
Can someone please help me out to understand why it is happening or how to find the problem here. Thank you in advance
It seems the problem was inside of my recycler view paging adapter what was causing the problem. I have solved the problem now.
It seems the problem was inside of my recycler view paging adapter what was causing the problem. I was instanciating the paging adapter but when setting it with recycler view there were some error in my code. Hence resulting the api not getting called at all.If anyone facing the same issue please check your paging adapter and see if it is getting assigned properly with your recyclerview.
so I make use of Picasso Library and when I run my Apps I get the error I use Androidx API 30 to create my apps.
this is the error: too many arguments for public open fun get() Picasso! defined in com.squareup.picasso.Picasso
this is my code :
class MainAdapter(val homeFeed: HomeFeed) : RecyclerView.Adapter<CustumViewHolder>() {
val videoTitles = listOf("First Title", "Second", "3rd")
override fun getItemCount(): Int {
return homeFeed.videos.count()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustumViewHolder {
val layoutInflater = LayoutInflater.from(parent?.context)
val cellForRow = layoutInflater.inflate(R.layout.video_row, parent, false)
return CustumViewHolder(cellForRow)
}
override fun onBindViewHolder(holder: CustumViewHolder, position: Int) {
val video = homeFeed.videos.get(position)
holder?.view?.textView_video_title?.text = video.name
holder?.view?.textview_channel_name?.text = video.channel.name
val thumbnailImageView = holder?.view?.imageView_video_thumbnail
Picasso.get(holder?.view?.context).load(video.imageUrl).into(thumbnailImageView)
}
}
class CustumViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
}
this is the line of code leading to the error above :
Picasso.get(holder?.view?.context).load(video.imageUrl).into(thumbnailImageView)
this is the screenshot of my error in my code :
enter image description here
You just have to pass your activity context to the Picasso.get(). Then it will be work fine for you.
Add this version of Picasso to your app/build.gradle file:
dependencies
implementation 'com.squareup.picasso:picasso:2.5.2'
}
please dont marked as duplicate , as the question is slightly different ---> null cannot be cast to non-null type kotlin.collections.MutableList
Scenerios:-
i have been performing delete cart using retrofit..
if atleast one item is present ,it displays in recyclerview
2.if cart is empty ,it crashes with a above error
here is my adapter code:-
class CartAdapter(context: Context, dataList: MutableList<DataCart?>) :
RecyclerSwipeAdapter<CartAdapter.CustomViewHolder>() { //added RecyclerSwipeAdapter and override
private var dataList: MutableList<DataCart>
private val context: Context
lateinit var dialog:ProgressDialog
var progressDialog: ProgressDialog? = null
inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val mView: View
val swipelayout:SwipeLayout
val productiamge: ImageView
val productname: TextView
val productcategory: TextView
val productprice: TextView
val quantity:TextView
val tvDelete:TextView
init {
mView = itemView
productiamge= mView.findViewById(R.id.imagecart)
productname= mView.findViewById(R.id.imagenamecart)
productcategory= mView.findViewById(R.id.imagecategory)
productprice =mView.findViewById(R.id.price)
quantity=mView.findViewById(R.id.quantity)
swipelayout=mView.findViewById(R.id.swipe)
tvDelete=mView.findViewById(R.id.tvDelete)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view: View = layoutInflater.inflate(R.layout.addtocart_item, parent, false)
return CustomViewHolder(view)
}
override fun getSwipeLayoutResourceId(position: Int): Int {
return R.id.swipe;
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
val progressDialog :ProgressDialog= ProgressDialog(context);
holder.productname.text = dataList.get(position).product.name ?: null
holder.productcategory.text = "(" +dataList.get(position).product.product_category +")"
holder.productprice.text = dataList.get(position).product.cost.toString()
Glide.with(context).load(dataList.get(position).product.product_images)
.into(holder.productiamge)
holder.quantity.text=dataList.get(position).quantity.toString()
holder.swipelayout.setShowMode(SwipeLayout.ShowMode.PullOut)
Log.e("checkidd",dataList.get(position).product.id.toString())
// Drag From Right
// Drag From Right
holder.swipelayout.addDrag(
SwipeLayout.DragEdge.Right,
holder.swipelayout.findViewById(R.id.bottom_wrapper)
)
val id =dataList.get(position).product?.id
holder.swipelayout.addSwipeListener(object : SwipeListener {
override fun onClose(layout: SwipeLayout) { }
override fun onUpdate(layout: SwipeLayout, leftOffset: Int, topOffset: Int) {
//you are swiping.
}
override fun onStartOpen(layout: SwipeLayout) {}
override fun onOpen(layout: SwipeLayout) {
}
override fun onStartClose(layout: SwipeLayout) {}
override fun onHandRelease(
layout: SwipeLayout,
xvel: Float,
yvel: Float
) {
}
})
holder.swipelayout.getSurfaceView()
.setOnClickListener(View.OnClickListener {
})
holder.tvDelete.setOnClickListener(View.OnClickListener {
view ->
val token :String = SharedPrefManager.getInstance(context).user.access_token.toString()
RetrofitClient.instancecart.deletecart(token,id!!)
.enqueue(object : Callback<DeleteResponse> {
override fun onFailure(call: Call<DeleteResponse>, t: Throwable) {
}
override fun onResponse(
call: Call<DeleteResponse>,
response: Response<DeleteResponse>
) {
var res = response
if (res.body()?.status==200) {
Toast.makeText(
context,
res.body()?.message,
Toast.LENGTH_LONG
).show()
progress()
mItemManger.removeShownLayouts(holder.swipelayout)
notifyItemChanged(position)
notifyItemRemoved(position)
dataList?.removeAt(position)
notifyItemRangeChanged(position, dataList?.size!!)
mItemManger.closeAllItems()
progressDialog.show()
}
else{
try {
val jObjError =
JSONObject(response.errorBody()!!.string())
Toast.makeText(
context,
jObjError.getString("message")+jObjError.getString("user_msg"),
Toast.LENGTH_LONG
).show()
} catch (e: Exception) {
Toast.makeText(context, e.message, Toast.LENGTH_LONG).show()
Log.e("errorrr",e.message)
}
}
}
})
mItemManger.bindView(holder.itemView, position)
})
}
override fun getItemCount(): Int {
return dataList.size
}
fun progress()
{
progressDialog?.dismiss()
val intent =
Intent(context.applicationContext, AddToCart::class.java)
intent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
context.applicationContext.startActivity(intent)
}
init {
this.context = context
this.dataList = dataList
}}
here is my activity:
class AddToCart:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.add_to_cart)
val totalamount:TextView=findViewById(R.id.totalamount)
val token: String =
SharedPrefManager.getInstance(
applicationContext
).user.access_token.toString()
RetrofitClient.instancecart.listcart(token).enqueue( object :
Callback<CartResponse> {
override fun onFailure(call: Call<CartResponse>, t: Throwable) {
Toast.makeText(applicationContext,"falied", Toast.LENGTH_LONG).show()
}
override fun onResponse(
call: Call<CartResponse>,
response: Response<CartResponse>
) {
val res=response
if (response.isSuccessful) {
val retro:List<DataCart> = response.body()!!.data
totalamount.setText(response.body()?.total.toString())
generateDataList(retro as MutableList<DataCart?>)
}
}
})
}
fun generateDataList( dataList:MutableList<DataCart?>) {
val recyclerView=findViewById<RecyclerView>(R.id.addtocartrecyleview) as? RecyclerView
val linear:LinearLayoutManager=
LinearLayoutManager(applicationContext,LinearLayoutManager.VERTICAL, false)
recyclerView?.layoutManager=linear
val adapter = CartAdapter(this#AddToCart,dataList)
recyclerView?.adapter=adapter
recyclerView?.addItemDecoration
(DividerItemDecorator(resources.getDrawable(R.drawable.divider)))
recyclerView?.setHasFixedSize(true)
adapter.notifyDataSetChanged()
if (dataList.isEmpty()) {
recyclerView?.setVisibility(View.GONE)
textviewempty.setVisibility(View.VISIBLE)
} else {
recyclerView?.setVisibility(View.VISIBLE)
textviewempty.setVisibility(View.GONE)
}
recyclerView?.addOnScrollListener(object :
RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
Log.e("RecyclerView", "onScrollStateChanged")
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
}
})
}
override fun onBackPressed() {
super.onBackPressed()
val intent = Intent(this, HomeActivity::class.java)
startActivityForResult(intent, 2)
}
}
i tried this-->by doing some changes-->
1--> var dataList: MutableList<DataCart?>
2--> var dataList: MutableList<>?=null
3--> var dataList: MutableList<>
Error log after doing Mutablelist to Arraylist
kotlin.TypeCastException: null cannot be cast to non-null type java.util.ArrayList<com.example.store.Cart.DataCart>
at com.example.store.Cart.AddToCart$onCreate$1.onResponse(AddToCart.kt:40)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:70)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:224)
at android.app.ActivityThread.main(ActivityThread.java:7147)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:536)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:876)
but nothing seems to be handling null
Please help me
Looks like it can be fixed by changing few lines:
Make CartAdapter accept nullable argument dataList, since your api request may return null and passing it would cause NPE.
class CartAdapter(context: Context, dataList: MutableList<DataCart?>?)
Since our dataList is nullable and calling dataList.size might throw NPE we need to make safe call using ?. And if it's null we just return 0, telling recyclerView that there are 0 items.
override fun getItemCount() = datalist?.size ?: 0
Need to make val retro:List<DataCart> nullable, because response.body()?.data may return null. We just convert retro to mutableList using extension function toMutableList(), with safe call operator "?". If retro is null then null value will be passed to CartAdapter, and since our adapter handles null value it will proceed without errors
if (response.isSuccessful) {
val retro:List<DataCart>? = response.body()?.data
totalamount.setText(response.body()?.total.toString())
generateDataList(retro?.toMutableList())
}
Remove init() function from CartAdapter and add var(actually should be val) before arguments in constructor. init() is redundant here because u r using it to assign values to redundant, duplicate member variables. By adding var(should be val) to constructor arguments they will be assigned values and be available as member variables, right after object construction.
Since dataList is nullable, and we need to determine its size for further logic safe call needs to be used, and if its null return true - empty
(dataList?.isEmpty() ?: true)
or use
`(dataList.isNullOrEmpty())`
which is cleaner, and should work too.
NOTE: Personally, i would'nt suggest you to retinitialize Adapter everytime you need to change values. Instead create val items = arrayListOf<DataCart>(). as a member variable and add a setter function for updating it, inside of which you would call notifyDatasetChanged() or other notify methods.
Can't find a reason to use MutableList but your issue is incorrect type cast (dataList as MutableList<DataCart>). This is cause null cannot be cast to non-null type. You can simplify code using class CartAdapter(private val context: Context, private val dataList: ArrayList<DataCart?>?) and remove var dataList: MutableList<DataCart?>, private val context: Context and init{}
Null check by using the Elvis operator in the part to set in the shopping cart
The first or second method seems to be good
I am having an issue where I imported some SVG drawables (that are optimised in Illustrator and have short path data - so their complexity is out of discussion) and displayed them in RecyclerView items. The problem is that, after testing the application many times, they stop working or they start rendering with glitches (like missing chunks or shapes). Weirdly enough, an app cache wipe resolves the issue and they work normally until after I ran the app from Android Studio about 5-6 times.
Here is what I mean by 'stopped working' :
In one activity they appear as red warnings, in another one they appear as a fingerprint icon (tho I do not have such an icon in the entire project, nor fingerprint implementation).
Here is the implementation:
I add the entries in room database like this:
Category(icon = R.drawable.ic_category_homepage)
where a category data class looks like this:
#Entity(tableName = "categories")
data class Category(
val title: String,
#DrawableRes
val icon: Int
)
So I add the SVG drawable reference as a DrawableRes Int in the local storage. Then, when I'm displaying the icon in the adapter, I use Glide:
Glide.with(context)
.load(category.icon)
.transition(DrawableTransitionOptions.withCrossFade())
.into(itemView.categoryIV)
Here is the entire adapter:
class DrawerAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val categories: ArrayList<Category> = ArrayList()
fun submitCategories(newFeed: List<Category>, lifecycleCoroutineScope: LifecycleCoroutineScope) {
lifecycleCoroutineScope.launch {
val result = coroutineRunOnComputationThread {
val oldFeed = categories
val result: DiffUtil.DiffResult = DiffUtil.calculateDiff(
DrawerDiffCallback(oldFeed, newFeed)
)
categories.clear()
categories.addAll(newFeed)
result
}
coroutineRunOnMainThread {
result.dispatchUpdatesTo(this#DrawerAdapter)
}
}
}
override fun getItemCount(): Int = categories.size
override fun getItemId(position: Int): Long {
return if (categories.isNullOrEmpty()) 0 else categories[position].id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return DrawerItemViewHolder(parent.inflate(R.layout.item_drawer_menu))
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) =
(holder as DrawerItemViewHolder).bind(categories[position])
inner class DrawerItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(category: Category) = with(itemView) {
Glide.with(context)
.load(category.icon)
.transition(DrawableTransitionOptions.withCrossFade())
.into(itemDrawerIVIcon)
if (category.preConfigured && category.resTitle != null)
itemDrawerTVTitle.text = context.resources.getString(category.resTitle)
else
itemDrawerTVTitle.text = category.title
}
}
private inner class DrawerDiffCallback(
private var oldFeed: List<Category>,
private var newFeed: List<Category>
) : DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldFeed[oldItemPosition]
val newItem = newFeed[newItemPosition]
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldFeed[oldItemPosition]
val newItem = newFeed[newItemPosition]
return oldItem == newItem
}
override fun getOldListSize(): Int = oldFeed.size
override fun getNewListSize(): Int = newFeed.size
}
}
Any idea why I get this weird behavior?
Hope this will resolve your glitching issue.
Picasso.get().load(category.icon)
.error(R.drawable.placeholder_round)
.placeholder(R.drawable.placeholder_round)
.resize(100, 100)
.into(itemDrawerIVIcon)
Just replace your Glide with Picasso with above config
I'm using the JetPack paging library with a network call (no database).
I am able to scroll down smoothly and load new pages of data, BUT, when scrolling up it stutters and quickly jumps to the top of the list. I am unable to scroll up smoothly.
Here is a video showing the problem: https://imgur.com/a/bRoelyF
What I've Tried:
Enabling retrofit caching
Using a LinearLayoutManager instead of GridLayoutManager
Following old and newer tutorials with versions 1.0.1 and 2.1.2 of the library
Here is my code:
MovieDataSource.kt:
private val movieDbApi: TheMovieDbApi
) : PageKeyedDataSource<Int, Movie>() {
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Movie>) {}
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Movie>) {
movieDbApi.getTopRatedMovies(BuildConfig.MOVIE_DATA_BASE_API, FIRST_PAGE).subscribe(
{
it?.let { callback.onResult(it.results, null, FIRST_PAGE + 1) }
}, {}
)
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Movie>) {
movieDbApi.getTopRatedMovies(BuildConfig.MOVIE_DATA_BASE_API, params.key).subscribe(
{
val key = params.key + 1
it?.let {callback.onResult(it.results, key)
}
},{}
)
}
MovieDataSourceFactory.kt:
class MovieDataSourceFactory(private val movieDbApi: TheMovieDbApi) :
DataSource.Factory<Int, Movie>() {
// Is this where the MovieDataSource callBacks are sent?
val movieLiveDataSource = MutableLiveData<MovieDataSource>()
override fun create(): DataSource<Int, Movie> {
val movieDataSource = MovieDataSource(movieDbApi)
movieLiveDataSource.postValue(movieDataSource)
return movieDataSource
}
}
HomeViewModel.kt:
class HomeViewModel #Inject constructor(
theMovieDbApi: TheMovieDbApi
) : DisposingViewModel() {
var moviePagedList: LiveData<PagedList<Movie>>
private var liveDataSource: LiveData<MovieDataSource>
init {
val movieDataSourceFactory = MovieDataSourceFactory(theMovieDbApi)
liveDataSource = movieDataSourceFactory.movieLiveDataSource
val config = PagedList.Config.Builder()
.setEnablePlaceholders(true)
.setPageSize(MovieDataSource.PAGE_SIZE)
.build()
moviePagedList = LivePagedListBuilder(movieDataSourceFactory, config)
.build()
}
}
HomeViewModel.kt:
class HomeActivity : AppCompatActivity() {
#Inject
internal lateinit var viewModelFactory: ViewModelFactory<HomeViewModel>
private lateinit var viewModel: HomeViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
AndroidInjection.inject(this)
val adapter = HomeAdapter()
movie_recycler_view.setHasFixedSize(false)
movie_recycler_view.layoutManager = LinearLayoutManager(this)
val viewModel = ViewModelProvider(this, viewModelFactory).get(HomeViewModel::class.java)
viewModel.moviePagedList.observe(this, Observer {
adapter.submitList(it)
})
movie_recycler_view.adapter = adapter
}
}
HomeAdapter.kt:
class HomeAdapter : PagedListAdapter<Movie, HomeAdapter.MovieViewHolder>(USER_COMPARATOR) {
override fun getItemCount(): Int {
return super.getItemCount()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_movie, parent, false)
return MovieViewHolder(view)
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
val movie = getItem(position)
movie?.let { holder.bind(it) }
}
class MovieViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(movie: Movie) {
Picasso.get().load(BASE_IMAGE_URL + movie.poster_path).into(itemView.movie_image)
}
}
companion object {
private val USER_COMPARATOR = object : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean =
oldItem == newItem
}
}
}
If anyone has a solution or spots a problem I'd love to hear it!
I solved the problem.
It's because I didn't add a placeholder image to Picasso in the adapter.
Before:
Picasso.get()
.load(BASE_IMAGE_URL + movie.poster_path)
.into(itemView.movie_image)
After:
Picasso.get()
.load(BASE_IMAGE_URL + movie.poster_path)
.placeholder(R.drawable.placeholder)
.into(itemView.movie_image)
Now it loads well.
Another consideration is the size of the image, it takes a while to load a larger image especially if you are loading many of them within an infinite scroll.