I am loading image stored in firebase firestore using Glide. Initially I was loading the image in the same activity where the url link is being fetched from Firebase. Things were running smooth till this point. Now I am trying to load this image in a popUp Activity (basically a different activity). The url is being fetched in the original activity and then being passed on to the popUp Activity.
I have checked that the URL passed to the new activity is correct, however, the same is not loading. Can some one help. The code in the new activity is as follows:
val catch = intent.getStringExtra("imgSrc")
popUpText.visibility = View.GONE
if(catch!=null){
val presImage = PhotoView(this#PopUp)
val param2: LinearLayout.LayoutParams = LinearLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,RelativeLayout.LayoutParams.WRAP_CONTENT, 1.0F)
param2.setMargins(0,64,0,0)
presImage.layoutParams = param2
presImage.scaleType = ImageView.ScaleType.FIT_CENTER
val popProgress = ProgressBar(this#PopUp)
popProgress.layoutParams = param2
popProgress.setBackgroundColor(Color.parseColor("#beb2bb"))
popLayout.addView(popProgress)
Glide.with(this#PopUp)
.load(Uri.parse(catch))
.listener(object: RequestListener<Drawable>{
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: com.bumptech.glide.request.target.Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: com.bumptech.glide.request.target.Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
popProgress.visibility=View.GONE
return false
}
})
.into(presImage)
}
The value of catch is correct. I have checked that by logging the same.
I understand that the error I may be making is likely very trivial, but have spent a lot of time unable to decipher.
Pls add your child view into parent view to show up on screen.
in that part of code above. you just loaded image into imageView but forgot add imageView into parent View.
Hope this help.
Related
I want to use palette to get a color and fill it before glide successfully loads the picture.
Just like bing.com or twitter.com, a color will cover the image before it is successfully loaded.
I use the following methods to achieve it:
Glide
.with(imageView.context)
.asBitmap()
.load(imageUrl)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(e: GlideException?, model: Any, target: Target<Bitmap>, isFirstResource: Boolean) = false
override fun onResourceReady(resource: Bitmap, model: Any, target: Target<Bitmap>, dataSource: DataSource, isFirstResource: Boolean): Boolean {
Palette
.from(resource)
.generate(PaletteAsyncListener { palette ->
val darkVibrantSwatch = palette!!.darkVibrantSwatch
val dominantSwatch = palette.dominantSwatch
val lightVibrantSwatch = palette.lightVibrantSwatch
if (darkVibrantSwatch != null) {
imageView.setBackgroundColor(darkVibrantSwatch.rgb)
} else if (dominantSwatch != null) {
imageView.setBackgroundColor(dominantSwatch.rgb)
} else {
imageView.setBackgroundColor(lightVibrantSwatch!!.rgb)
}
})
return false
}
})
.into(imageView)
However, through the above code, I will load the image directly (imagview will display white before the image is loaded successfully), without filling it with solid color first.
I think maybe glide is loading too fast?
Is there any way to ensure that my picture is filled with solid color for a period of time, such as 500ms?
use Palette.from(resource!!.toBitmap) this will force and rap the resources to bitmap. but firstly you will need to use let on the resources variable so that resources doe's not give you issues
I'd like to set a marker on a Google Map displayed by an Android Kotlin app to be a URL of my choice. It's clear that fetching the URL content needs to be done off the UI thread, and that coroutines are the approach here, so I'd like to run a few lines of code to fetch the URL and put it into a BitmapDescription object inside a coroutine, and then use that BitmapDescription to call setIcon on the Marker object to set the custom image.
I already have a Marker, and a URL. So I tried this:
uiScope.launch(Dispatchers.IO) { // not sure this is the best way to launch in IO
val furl = URL(myURL)
val bm = BitmapFactory.decodeStream(furl.openConnection().getInputStream())
val bd = BitmapDescriptorFactory.fromBitmap(bm)
uiScope.launch(Dispatchers.Main) { // go back to UI thread; this crashes
marker.setIcon(bd)
}
}
This is obviously not right because it crashes. The fetch of the URL and creating a BitmapDescriptor seems to be working fine as far as I can tell; once I have that BitmapDescriptor, how do I call marker.setIcon with it?
Although you said that fetching the image and creating BitmapDescriptor seems to be working fine, I can almost say that it is not right to do it yourself using an URL connection. The procedure of fetching and decoding images might involve many possible errors that could not be handled this way. It's better to delegate this responsibility as well as thread switching to a reliable tool such as Glide.
Let's write an extension function for the Marker using Glide in a kotlin file:
ExtensionFunctions.kt
import android.content.Context
import android.graphics.Bitmap
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.Marker
fun Marker.loadIcon(context: Context, url: String?) {
Glide.with(context)
.asBitmap()
.load(url)
.error(R.drawable.default_marker) // to show a default icon in case of any errors
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Bitmap>?,
isFirstResource: Boolean
): Boolean {
return false
}
override fun onResourceReady(
resource: Bitmap?,
model: Any?,
target: Target<Bitmap>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
return resource?.let {
BitmapDescriptorFactory.fromBitmap(it)
}?.let {
setIcon(it); true
} ?: false
}
}).submit()
}
Now. it's just enough to call it over a marker object to load the image for it asynchronously:
marker.loadIcon(context, url)
I'm trying to implement the android library SelectionTracker which allows to select items in a recyclerView.
Everything works fine except that when I click outside of an
item (which is in a grid layout), the all selection is cleared.
I actually have found the code which calls the clearSelection(). It's on the line 78 of the class TouchInputHandler.
It then calls the line 64 of ItemDetailsLookup which returns false because the touch event didn't occurred on an item.
I was wondering if anyone have found a workaround to prevent this behavior, because I didn't found any option in the documentation.
It's a gridLayout so it is quite "normal" to have space between items and I don't want my users to clear the selection because they have touch the side of an item.
This is my solution, based on that if we have predefined ItemDetail that will be used as "this is not the view you can select".
First, inside your ItemDetailsLookup instead of returning null you can pass single item with distinguish data that will make sure there is no name/position collision with any other data you can have
class AppItemDetailsLookup(private val rv: RecyclerView) : ItemDetailsLookup<String>() {
override fun getItemDetails(e: MotionEvent): ItemDetails<String>? {
val view = rv.findChildViewUnder(e.x, e.y) ?: return EMPTY_ITEM
return (rv.getChildViewHolder(view) as AppItemViewHolder).getItemDetails()
}
object EMPTY_ITEM : ItemDetails<String>() {
override fun getSelectionKey(): String? = "empty_item_selection_key_that_should_be_unique_somehow_that_is_why_i_made_it_so_long"
override fun getPosition(): Int = Integer.MAX_VALUE
}
}
And then when you are creating SelectionTracker with builder, instead of using standard predicate (default is SelectionPredicates.createSelectAnything()) you make your own that will notify that this EMPTY_ITEM cannot be selected
.withSelectionPredicate(object : SelectionTracker.SelectionPredicate<String>() {
override fun canSelectMultiple(): Boolean = true
override fun canSetStateForKey(key: String, nextState: Boolean): Boolean =
key != AppItemDetailsLookup.EMPTY_ITEM.selectionKey
override fun canSetStateAtPosition(position: Int, nextState: Boolean): Boolean =
position != AppItemDetailsLookup.EMPTY_ITEM.position
})
I tested it with LinearLayoutManger, the selection was deselecting all items once i clicked outside any of them (my items did not had spacing decoration, but there were so few of them that i was seeing empty under last item)
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.
My app has a RecyclerView which support drag items to change their order.
My app use ViewModel, Lifecycle, Room before adding paging library. And code to handle drag is easy.
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val oPosition = viewHolder.adapterPosition
val tPosition = target.adapterPosition
Collections.swap(adapter?.data ,oPosition,tPosition)
adapter?.notifyItemMoved(oPosition,tPosition)
//save to db
return true
}
However, after I use paging library,
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val oPosition = viewHolder.adapterPosition
val tPosition = target.adapterPosition
Collections.swap(adapter.currentList,oPosition,tPosition)
adapter.notifyItemMoved(oPosition,tPosition)
return true
}
my app crashed because PagedListAdapter.currentList do not support set.
java.lang.UnsupportedOperationException
at java.util.AbstractList.set(AbstractList.java:132)
at java.util.Collections.swap(Collections.java:539)
at gmail.zebulon988.tasklist.ui.TaskListFragment$MyItemTouchCallback.onMove(TaskListFragment.kt:119).
Then I change the code
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val oPosition = viewHolder.adapterPosition
val tPosition = target.adapterPosition
Log.d("TAG","onMove:o=$oPosition,t=$tPosition")
val oTask = (viewHolder as VH).task
val tTask = (target as VH).task
if(oTask != null && tTask != null){
val tmp = oTask.order
oTask.order = tTask.order
tTask.order = tmp
tasklistViewModel.insertTask(oTask,tTask)
}
return true
}
This code change the task's order in db directly and the library update the display order by the db change. However, the animation is ugly.
Is there a way to use onMove and paging library together genteelly?
When you use a PagedList with Room you often tie it up so that the updates to the underlying data are reflected automatically via LiveData or Rx, and such an update happening in a background can always mess up your drag and drop. So IMHO you can't make it 100% bulletproof for all situations. Having said that, you can create (I almost said "hack together") a shim that will do what you want. This involves several pieces:
You need to hold the indexes of the items being swapped in your adapter
You need to override getItem() in the adapter and make it "swap" the items for you instead of swapping them using Collections.swap
You need to delay the actual item updating via Room until the item is dropped, at which point you also clear your "swapping in progress" state. Something along these lines:
fun swapItems(fromPosition: Int, toPosition: Int) {
swapInfo = SwapInfo(fromPosition, toPosition)
notifyItemMoved(fromPosition, toPosition)
}
override fun getItem(position: Int): T? {
return swapInfo?.let {
when (position) {
it.fromPosition -> super.getItem(it.toPosition)
it.toPosition -> super.getItem(it.fromPosition)
else -> super.getItem(position)
}
} ?: super.getItem(position)
}
fun clearSwapInfo() {
swapInfo = null
}
This way you will get a smooth dragging experience as long as there are no background updates for your list and you stay within already loaded list of items. It gets much more complicated if you need to be able to drag through a "refill".
You need to heck for moving items in PagedList.
Recyclerview's adapter needs to do two things perfectly if you want to drag items up and down for moving them. First is to swap two items in datalist, second is to notify cells re-render.
re-render is easy, you can use notifyItemMoved to update layout when moving, but PagedList is immutable, you cannot modify it.
And there is an animation bug when the cell ui has already changed but the datasource did not. You cannot override the render logic in inner of recyclerview, but you can heck the result of PagedStorageDiffHelper.computeDiff to fix the animation bug.
At last, dont forget to retrieve the most updated data after the drag and drop.
//ItemTouchHelperAdapter
override fun onItemStartMove() {
//the most the most updated data; mimic pagedlist, but can be modified;
tempList = adapter.currentList?.toMutableList()
toUpdate = mutableListOf()
}
override fun onItemMove(fromPosition: Int, toPosition: Int): Boolean {
val itemFrom = tempList?.get(fromPosition) ?: return false
val itemTo = tempList?.get(toPosition) ?: return false
//change order property for data itself
val order = itemTo.order
itemTo.order = itemFrom.order
itemFrom.order = order
//save them for later update db in batch
toUpdate?.removeAll { it.id == itemFrom.id || it.id == itemTo.id }
toUpdate?.add(itemFrom)
toUpdate?.add(itemTo)
//mimic mutable pagedlist, for get next time get correct items for continuing drag
Collections.swap(tempList!!, fromPosition, toPosition)
//update ui
adapter.notifyItemMoved(fromPosition, toPosition)
return true
}
override fun onItemEndMove() {
tempList = null
if (!toUpdate.isNullOrEmpty()) {
mViewModel.viewModelScope.launch(Dispatchers.IO) {
//heck, fix animation bug because pagedList did not really change.
NoteListAdapter.disableAnimation = true
mViewModel.updateInDB(toUpdate!!)
toUpdate = null
}
}
}
//Fragment
mViewModel.data.observe(this.viewLifecycleOwner, Observer {
adapter.submitList(it)
//delay for fix PagedStorageDiffHelper.computeDiff running in background thread
if (NoteListAdapter.disableAnimation) {
mViewModel.viewModelScope.launch {
delay(500)
adapter.notifyDataSetChanged() //update viewholder's binding data
NoteListAdapter.disableAnimation = false
}
}
})
//PagedListAdapter
companion object {
//heck for drag and drop to move items in PagedList
var disableAnimation = false
private val DiffCallback = object : DiffUtil.ItemCallback<Note>() {
override fun areItemsTheSame(old: Note, aNew: Note): Boolean {
return disableAnimation || old.id == aNew.id
}
override fun areContentsTheSame(old: Note, aNew: Note): Boolean {
return disableAnimation || old == aNew
}
}
}
I had a slightly different problem and #dmapr's answer has finally led me to the solution after hours of debugging.
For me the issue was that the item I just moved suddenly jumped back to its previous position after the db was updated and the call to submitData was made. Basically the drag and drop action is sort of canceled, however the order with all the relevant data is correct in the database, and if I was to call notifyDataSetChanged() I'd see the real list where all items are where they should be. Here's what has worked for me:
class SomePagingAdapter(
private val onItemMoveUpdate: (fromPos: Int, toPos: Int) -> Unit,
) : PagingDataAdapter<Model, SomePagingAdapter.ViewHolder>(diffUtil), ItemMoveCallback {
companion object {
private val diffUtil = /* ... */
}
private var swapInfo: SwapInfo? = null
// viewHolder methods, etc.
// Called in touch helper's onMove
override fun onItemMove(fromPos: Int, toPos: Int) {
notifyItemMoved(fromPos, toPos)
}
// Called in touch helper's clearView() to save the result of this drag and drop
override fun onItemFinishedMove(fromPos: Int, toPos: Int) {
swapInfo = SwapInfo(fromPos, toPos)
onItemMoveUpdate(fromPos, toPos)
}
fun adjustRecentSwapPositions() {
// "Undo" the notifyItemMoved we did before that messed up positions
swapInfo?.let { swap ->
notifyItemMoved(swap.toPos, swap.fromPos)
}
swapInfo = null
}
}
interface ItemMoveCallback {
fun onItemMove(fromPos: Int, toPos: Int)
fun onItemFinishedMove(fromPos: Int, toPos: Int)
}
data class SwapInfo(val fromPos: Int, val toPos: int)
It's important that submitData is suspended and adjustRecentSwapPositions is called immediately after. Watch out for that if you use RxJava.
scope.launch {
flow.collectLatest { pagingData ->
adapter.submitData(pagingData)
adapter.adjustRecentSwapPositions()
}
}
It works great and recycler's animations are fine.