RecyclerView load 10000+ image from backend api - android

I get JSON data in my back-end API.
[
{
id: "is random"
image: "my url"
},
......10000+
]
My id is random number without continuity.
Each json object has image.
I call api and put data in RecyclerView .
But when RecyclerView is displayed, it will get stuck or crash.
I use Glide load image.
GlideApp.with(context).load(myUrl).placeholder(myPlaceholder).into(uiImage)
How can I let RecyclerView read it smoothly?
Can I use Paging Library to let it display five data at the beginning?
But I don't know which kind of DataSource is suitable for my json format.
ItemKeyedDataSource? or PageKeyedDataSource? or PositionalDataSource?
Update:
class MyAdapter(): ListAdapter<Item, ItemHolder>(Item.DIFF_CALLBACK){
override fun onCreateViewHolder(parent: ViewGroup, type: Int): ItemHolder=
ItemHolder(LayoutInflater.from(parent.context), parent).also {}
override fun onBindViewHolder(holder: ItemHolder, position: Int): Unit =
with(holder) {
val item = getItem(position)
uiTextView.text = item.id
GlideApp.with(context).load(item.image).placeholder(myPlaceholder).into(uiImage)
}
}
Update:
class ItemHolder(inflater: LayoutInflater, parent: ViewGroup)
: RecyclerView.ViewHolder(inflater.inflate(R.layout.my_Item, parent, false)) {
val uiTextView: TextView = itemView.findViewById(R.id.my_textView)
val uiImage: ImageView = itemView.findViewById(R.id.my_image)
}
Sometimes it crashes if I remove the placeholder.
GlideApp.with(context).load(myUrl).into(uiImage)
Log message:
I/Choreographer: Skipped 3660 frames! The application may be doing too much work on its main thread.

inject GlideApp, instead of creating new instance every-time, which will make your app more smooth.
And also if you are using Paging Library, then use PagedListAdapter.

Related

How to load textView and imageView (from strings & drawables) into a fragment navigated to, from recyclerView

I asked a similar question here. But I feel I didn't express myself vividly. So I'm elaborating.
I want to load textView and imageView (from strings & drawables) on the fragment that is navigated to, from recyclerView.
Please note I am using navigation component.
I am kinda new to this, but I'm done some reach and find out that I could used intent, and just navigate to an activity and use intent putExtra to load whatever I want to load, but I am using single activity and mutiple fragment model. So I can't do this.
I have been at this for over a week, so I'd really appreciate any help. Even if it's just the name of the tool I need to use.. But I wouldn't mind some code, thanks a lot for your help, in advance.
I don't know what info you will need, so I'll just add what I feel is important. I will provide any info if requested thanks.
Here's the recyclerView Adapter, that navigate into the fragment:
class StoriesRecyclerAdapter(private val storiesList: ArrayList<StoriesDataClass>) : RecyclerView.Adapter<StoriesRecyclerAdapter.ViewHolder>() {
// create new views
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
...
return ViewHolder(view)
}
// binds the list items to a view
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
...
// navigates to stories content destination
holder.storiesCardView.setOnClickListener {
val action = StoriesFragmentDirections.actionNavigationStoriesToStoriesContentFragment()
holder.itemView.findNavController().navigate(action)
}
}
// return the number of the items in the list
override fun getItemCount(): Int {
...
}
// Holds the views for adding it to image and text
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val storiesCardView : CardView = itemView.findViewById(R.id.stories_cardView)
...
}
}
Please inform me if you need any more info, I sinerely thank you for your time and feedback.
you should use safe args navigaiton component
check this link out its pretty easy to work
https://developer.android.com/guide/navigation/navigation-pass-data
you can use intent.extra to pass data
findNavController(R.id.main_content).setGraph(R.navigation.<you_nav_graph_xml>, intent.extras)
or bundle to pass data
val bundle = Bundle()
bundle.putString("some_argument", "some_value")
navController.setGraph(R.navigation.<you_nav_graph_xml>, bundle)

Fragment within Fragment: Saving Recycler's View Adapter when pressing Back Button ¿?

First question in the community :)
I am building an app in which I have a MainActivity and fragments everywhere.
Inside a fragment I have another fragment which has a RecyclerView.
I load the elements of the recyclerview from Cloud Firebase and it works perfectly!
The thing I have a problem with is the time it takes to load every time I close and enter the same fragment.
For example: When I first load this fragment, it takes a second or two to get the data and parse it into objects etc etc... to finally show the recyclerView as I mean to.
BUT when I press the back button and enter the fragment again, it takes 2 seconds (aprox) again.
Is there a way to make the request only one time, save the Adapter (or the response) and then set it up again?
I have read about onSaveInstanceState(), I have read about SavedInstanceState, I tried setting a counter, setting a boolean, but I still can't find a way to STORE the information in a way that it is still alive when I shut down the fragment.
Hope this makes sense,
Thanks in advance!
EDIT:
fun getObjects(): FirestoreRecyclerAdapter<Object, ObjectViewHolder> {
val firestore = FirebaseFirestore.getInstance()
val query = firestore.collection("collectionPath")
val response = FirestoreRecyclerOptions.Builder<Object>()
.setQuery(query, Object::class.java).build()
val adapter = object : FirestoreRecyclerAdapter<Object, ObjectViewHolder>(response) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ObjectViewHolder {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.element_object,
parent, false
)
return ObjectViewHolder(view)
}
override fun onBindViewHolder(holder: ObjectViewHolder, position: Int, object: Object) {
val title = (position + 1).toString() + ". " + object.title
holder.itemView.element_object_title.text = title
val image = holder.itemView.element_object_image
Picasso.with(holder.itemView.context).load(object.image).into(image)
holder.object = object
}
}
return adapter
}

Gif loaded with Glide in RecyclerView Adapter not playing properly

I have a recyclerview which shows a list of images in horizontal view. I want to show a gif around each imageview like an outline similar to instagram's story view.
Here is the code for loading the same.
GlideApp
.with(itemView.context).asGif()
.load(R.drawable.red)
.error(R.drawable.red)
.diskCacheStrategy(DiskCacheStrategy.DATA)
.into(ivBackground)
I tried adding this code on both places i.e. onCreateViewHolder as well as onBindViewHolder but the gif is not playing properly.
It only shows one frame at a time. If I pause current activity by pressing the back button and then from recent app menu, navigate back to the app, then it shows the next frame.
My gif is stored in the drawable folder and I have the exact same gif working properly in an activity/fragment.
Issue is it's not playing properly inside a recyclerview adapter.
This is the complete adapter class.
class LiveViewAdapter(
val context: Context,
val liveList: ArrayList<DataItem>,
private val listener: LiveViewAdapterListener
) :
RecyclerView.Adapter<LiveViewHolder>() {
private val inflater: LayoutInflater
interface LiveViewAdapterListener {
fun onLiveClicked(url: String)
}
init {
inflater = LayoutInflater.from(context)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LiveViewHolder {
val view = inflater.inflate(R.layout.item_live, parent, false)
return LiveViewHolder(view)
}
override fun getItemCount(): Int {
return liveList.size
}
override fun onBindViewHolder(holder: LiveViewHolder, position: Int) {
val requestOptions = RequestOptions().apply {
placeholder(R.mipmap.ic_launcher_round)
error(R.mipmap.ic_launcher_round)
}
val videoId = liveList[holder.adapterPosition].liveUrl?.substring(liveList[holder.adapterPosition].liveUrl?.lastIndexOf("=")!! + 1)
val imageUrl = "https://img.youtube.com/vi/$videoId/hqdefault.jpg"
Glide.with(context)
.load(liveList[position].liveUrl)
.apply(requestOptions)
.thumbnail(Glide.with(context).load(imageUrl))
.into(holder.ivLive)
holder.ivLive.setOnClickListener {
listener.onLiveClicked(liveList[holder.adapterPosition].liveUrl!!)
}
}
}
Glide version used
implementation 'com.github.bumptech.glide:glide:4.8.0'
kapt 'com.github.bumptech.glide:compiler:4.8.0'
UPDATE
I figured out that the above code works fine on android devices which have sdk version less than pie. On devices with android pie, it's not working properly.
Are you using roundedImageView or just native imageview ?
I met the same the same problem while using roundedImageView. I replace it with native ImageView, the gif play.

Populating RecyclerView with two sets of data from Firebase

I have Firebase set up as so...
{
"items" : {
"-LDJak_gdBhZQ0NSB-7B" : { //item
"name" : "Test123",
...
},
...
},
"likes" : {
"YoTjkR2ORlcr5hGedzQs5xOK2VE3" : { //user
"-LDJiY0YSraa_RhxVWXL" : true //whether or not the item is liked
},
...
}
}
And I'm populating a RecyclerView with items, but I need to know if each item has been liked by the current user. I know I could store each user who has liked an item in the item itself, but that seems like too much repetition. But even if done that way, I'd have to check through a list of users who liked an item for that specific user, for each item added. Should I just live with all of this repetition or is there an easy way to deal with this? Here's the code I have that uses FirebaseRecyclerAdapter:
private fun setUpFirebaseAdapter() {
val ref = FirebaseDatabase.getInstance().reference
val itemQuery = ref
.child("items")
.limitToLast(20)
val itemOptions = FirebaseRecyclerOptions.Builder<Item>()
.setQuery(itemQuery, Item::class.java)
.build()
firebaseAdapter = object : FirebaseRecyclerAdapter<Item, FirebaseViewHolder>(itemOptions) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FirebaseViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_row, parent, false)
return FirebaseViewHolder(view)
}
override fun onBindViewHolder(holder: FirebaseViewHolder, position: Int, model: Item) {
holder.bindItems(model)
//...
}
}
}
I know there's index support, but I think that would only allow me to only return liked items.
But that seems like too much repetition.
Yes, it is.
Should I just live with all of this repetition or is there an easy way to deal with this?
Yes, you should!
In such cases as yours, you should use this tehnique which is named in Firebase denormalization, and for that I recomend you see this tutorial, Denormalization is normal with the Firebase Database, for a better understanding.
So, there is nothing wrong in what you are doing, besides that is a common practice when it comes to Firebase.

Paging Library: How to reload portion of data on demand?

I use Paging Library to paginate my data set. What I'm trying to do is to refresh the RecyclerView after data in my database has been changed.
I have this LiveData:
val listItems: LiveData<PagedList<Snapshot>> = object : LivePagedListProvider<Long, Snapshot>() {
override fun createDataSource() = SnapshotsDataSource()
}.create(null, PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(PAGE_SIZE)
.setEnablePlaceholders(false)
.build()
)
And the DataSource:
class SnapshotsDataSource : KeyedDataSource<Long, Snapshot>(), KodeinGlobalAware {
val db: FirebaseDb = instance()
override fun getKey(item: Snapshot): Long = item.timestamp
override fun loadInitial(pageSize: Int): List<Snapshot> {
val result = db.getSnapshotsTail(pageSize)
return result
}
override fun loadAfter(key: Long, pageSize: Int): List<Snapshot> {
val result = db.getSnapshotsTail(key, pageSize)
return result.subList(1, result.size)
}
override fun loadBefore(key: Long, pageSize: Int): List<Snapshot> {
return emptyList()
}
}
The Adapter is straight forward, so i omit it here.
I've tried to do this when database is modified:
fun reload(position) {
listItems.value!!.loadAround(position)
}
but it didn't help.
try to call listItems.value!!.datasource.invalidate()
not directly DataSource#invalidate()
I am having the same issue with a custom Firestore based DataSource. The only way to load a portion of the data without invalidating all of the data and having the UI flash / reload seems to be via integrating with Google's Room ORM library. Unfortunately, this will cache my data twice, once with Firestore, and again with Room which is unnecessary.
See the documentation under Consider How Content Updates Work. The only way to have realtime updates is via implementing Room with the PagedList: If you're loading data directly from a Room database updates get pushed to your app's UI automatically.
It's not possible. You can invalidate the whole list only: datasource.invalidate().

Categories

Resources