As we know in google maps android version 2, a custom information windows, is a view that converts to an image. Actually google aftre you return you view converts the view to a image an then show it to the info windows.
But I want to show images downloading from internet to my infowindow. Actually, I want that before that download complete show an progress bar to my infowindows and after it compeleted update info windows, by this mechanism how I can do this?
Have a model object that contains int for progress and Bitmap for actual image.
Start some kind of background operation started (e.g. AsyncTask) to download image.
Update model's progress from AsyncTask.onProgressUpdate.
Update model's image from AsyncTask.onPostExecute.
Have an observer for model (see Observer partern).
If you keep AsyncTask (or any other kind of Thread in Activity context, don't forget to cancel it and you may also skip observer pattern.
Save reference to marker showing info window inside InfoWindowAdapter.getInfoWindow. Call it markerShowingInfoWindow.
When you are notified of progress update,
call:
if (markerShowingInfoWindow != null && markerShowingInfoWindow.isShowingInfoWindow()) {
markerShowingInfoWindow.showInfoWindow();
}
To force InfoWindowAdapter.getInfoWindow call and create ViewGroup containing ProgressBar and ImageView from progress and image values inside model.
As always, translate this into C# world.
Related
I have a Recyclerview which shows one detail card on full screen. Initially basic data loading is done when data is set in Adapter from one Main Api Call. when card is set on screen using PagerSnapHelper in centre, to show some specific details on detail card I want to make separate Api call to different Api endpoints and update the same POJO object for that particular Card. The trigger point for making the separate extra Api call fro details is when card is in Idle position hit and get data. This is working fine. The code is
binding.rvUserData.addOnScrollListener(
SnapOnScrollListener(
snapHelper,
SnapOnScrollListener.Behavior.NOTIFY_ON_SCROLL_STATE_IDLE,
object : OnSnapPositionChangeListener {
override fun onSnapPositionChange(position: Int) {
val getData = viewModel.getExtraUserDetails()
val currentUserData: UserData? = adapter?.getCurrentData()?.get(position)
currentUserData.userPreferences = getData.preferences
currentUserData.userSkills = getData.skills
adapter?.updateData(position,currentUserData)
}
})
)
This is working fine. But suppose the Card has already done the extra details Api Call and set the data in onBindViewHolder it again call the same when we scroll to same position. How to skip the extra unnecessary Api call when data is already set on Recyclerview card and only make a call for extra data when Card is recycled etc or does not have extra data to show full details.
Use Case:
Card-1 displayed (extra Api calls made and data is set)
Scroll to next Card-2 (extra Api calls made and data is set)
Scroll again to Card-1 (again extra Api calls made and data is set)
How to make sure it should make Api calls when necessary ? How to resolve this ? It would be of great help, thanks
Context
So, I don't know if any of you has ever gone through the same situation but I've recently taken over an app and we have this RecyclerView on the main screen - because of an NDA I'll change a few things - that shows a list of apartments that you can rent - picture the AirBnB app - and if you tap on one of these apartment items you go to the apartment detail, where you have a bit more of functionality and features.
The thing is that we have way too many moving parts on the apartment list. For example, on each apartment ViewHolder you can:
Use a checkmark to specify if you are going to bring any pets with you.
A few UI items to specify how long are you going to stay.
An EditText to set how may people are going to come.
A Rent button that turns itself into a spinner and sends an API call.
A More Options button that expands the ViewHolder, showing a LinearLayout with yet more UI.
Picture something like this
This is actually a simpler example of what I really have. Let me tell you that it looks as if each ViewHolder could be a Fragment because of all the functionality that we have on each.
Now what's the problem here?
Recycling issues. If you scroll off, and scroll back to the same position you are supposed to keep the same state that you had on that ViewHolder, right? If you had checked a CheckButton that's supposed to be check. If you had written something on an EditText, that's supposed to be there. If you had expanded the More Options section, that's supposed to be expanded. You see where I'm going at?
What am I asking here?
Well, about feedback for a possible solution or improvement. I know what most of you would tell me here - because it is the same thing I thought at first - just move all that functionality into the apartment detail, keep that list as simple as possible. But it is not as simple, we have a large user base who is already used to this UI. Changing things so abruptly is not an option.
What do I have right now?
In my RecyclerView adapter I keep a collection of "State" objects which I use to save/restore the ViewHolder states, but it is getting way too big and way too complex. This may sound crazy, but it is there such thing as having a RecyclerList of Fragments? I just don't want to worry/bother about keeping the states of these ViewHolder anymore.
Notes
Sorry I haven't provided any code, but there's not much to show actually, as you may imagine the onBindViewHolder is just a humongous piece of code that sets the views with the data I fetch from the API plus the data that I store in these "State" objects. I save these "State" objects via the onViewDetachedFromWindows() hook from the adapter class that gets triggered when a ViewHolder scrolls off from screen. I wipe out these "State" objects when I fetch a new API response.
Any feedback is appreciated,
Thanks!🙇
Your post is vague in it's high-level description but I'll try to comment in a similar manner that may guide you towards solutions.
First, as was already mentioned Epoxy is a thing. As is adapter delegates. You may find those useful. However, you don't need a library to solve you problem - you need separation of concerns and architecture.
The thing is that we have way too many moving parts on the apartment list.
OK, so first suggestion is to stop having too many moving parts in the list. Each thing you listed could / should be it's own (custom) view that is driven by it's own ViewModel. A recycler view / view holder / adapter should be as stupid as possible. All those things should be doing is filling in boilerplate that Android requires. Actual logic should exist elsewhere.
If you scroll off, and scroll back to the same position you are supposed to keep the same state that you had on that ViewHolder, right?
No. Your ViewHolder should not maintain state. A ViewHolder holds views so Android doesn't have to re-inflate stuff over and over. It should not keep track of its state - it should be told what its current state is.
You should have a list of data objects (view models) that represent the current state of each item in the list. When you scroll off and back to the same position, you are supposed to re-bind the item that should be at that position to the view that represents it. Saving and clearing "state" objects should not be necessary - you should always have the current state on hand because it's the underlying data model driving your whole UI.
In my RecyclerView adapter I keep a collection of "State" objects which I use to save/restore the ViewHolder states, but it is getting way too big and way too complex
If something is too big and complex, break it down. Instead of having one giant-ass state object for each item, use composition. Make this item state have properties that represent pieces of the UI - PetModel, DateRangeModel, etc.
This may sound crazy, but it is there such thing as having a RecyclerList of Fragments? I just don't want to worry/bother about keeping the states of these ViewHolder anymore.
That does sound crazy because not only would this not solve your problem, you would probably actually make it significantly worse. You don't want to manage the state of a bunch of ViewHolders but you want to manage the states of a bunch of Fragments!? Bruh.
as you may imagine the onBindViewHolder is just a humongous piece of code that sets the views with the data I fetch from the API plus the data that I store in these "State" objects.
Again, break that up. You should not be slapping "data I fetched from the API" directly onto views. Invariably you will need to massage and transform raw data from an API before you display it. This should be handled by a dedicated object (again, ViewModel or some other structure). Again, views should be dumb. Tell them their state and that's it - don't do logic at this level.
Please read the Android Architecture Guide.
Also Google around for "Clean Architecture" - that seems to be all the range in Android these days.
And finally - here's some very rough pseudocode of how you could structure this to be more testable and maintainable.
From the bottom up:
ApiClient - responsible for just fetching the raw data from the API
endpoint or reporting an error.
ApiResponseModel - language-specific object representation
of the data you'll get from the API. Has info on the pet, dates,
guest count, etc. May contain submodels.
ItemDomainModel - client side representation of your data after transforming the data you'll get from the API.
Repository - uses the ApiClient to fetch the data as ApiResponseModel and transforms it into a ItemDomainModel object that makes more sense for your app.
ItemViewModel - Represents the UI state of a single item in the RecyclerView. Takes a ItemDomainModel instance and exposes the state of the UI based on the state of that model. This can be broken down if it's too complex (PetStateViewModel, DateRangeViewModel, GuestCountViewModel, etc)
ListViewModel - The top-level Android ViewModel that represents the state of the screen. Uses the Repository to fetch the data then constructs a list of ItemViewModels to feed into the RecyclerViewAdapter.
If you get those pieces in place, your view binding in the adapter should be stupid dumb:
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
// The adapter list should be a list of view models populated by the
// fragment after the ListViewModel returns a list of them from the fetch
val itemViewModel = itemViewModels[position]
// Populating this item view should just be a one-to-one mapping of the view model
// state - NO LOGIC. Dumb. Stupid. Tonto.
viewHolder.bringingPets.isChecked = itemViewModel.isBringingPets
viewHolder.guestCount.text = itemViewModel.guestCount
// ... etc, etc (if you use databinding this is a one-liner and even stupider)
// Set up your event listeners so interacting with this specific item in the list
// updates the state of the underlying data model
viewHolder.bringingPets.setOnCheckChanged { itemViewModel.isBringingPets = it.isChecked }
viewHolder.rentButton.onClickListener { itemViewModel.rentThis() }
// ... etc, etc
}
The goal is to do as little as possible here. Just update the state and wire up your callbacks that just delegate back to the ViewModel. Then, those UI states are driven by the logic in the view model. This is where you do business logic that determines how the UI should look.
class ItemViewModel(private val dataModel: ItemDomainModel) {
var isBringingPets: Boolean
get() = /* some business logic that determines if the checkbox is checked */
set(value) /* update underlying state and notify of changes */
// ... etc, etc, for guest count and other properties
fun rentThis() {
// Fire an event or update live data or invoke a callback that
// the fragment can use to respond
}
// ... etc, etc, for other functions that respond to UI events
}
In Summary
Refactor your code to break down the huge and complex logic into dedicated components that each have a simpler, specific focus, then compose them together to get the behavior you want. Good luck.
I use a loader for sqlite, handler thread (looper with queue) and a listview,
I need to show the user a progress bar while downloading the file inside the listview.
I'm thinking of the below but it doesn't look right as I'll be changing the data source a lot,
1 - User clicks "Download file icon"
2 - Video starts downloading
3 - Update the sql table with the progress
4 - Notify the loader to reload the data
5 - I have the code that keeps the list view at its current position after changing the data source
I can't think of another solution, is there any alternatives?
I'm building an app that should make it possible to browse the images of an internet site with lots of galleries and photos. Here are the steps that the app is going through:
Extract URLs to albums and their images from rss feed
Build a ListView with every Album and one thumbnail
The ListView's adapter getView() method sets every item's ImageView to a placeholder, which will be replaced with the real thumbnail by the corresponding AsyncTask
The Adapter's getView() then executes an AsyncTask which fetches the image
The AsyncTask checks the cache first and if the picture is not there, it downloads
Using WeakReferences, the AsyncTasks update (or not) the desired View with the aquired image, thus replacing the placeholder.
When an Album is opened, I go through the same procedure as in step 2 but use a GridView instead of the ListView to display the album contents.
/* GridViewAdapter.getView(...) follows */
public View getView(int position, View convertView, ViewGroup parent)
ImageView thumbnail;
// Recycle or not...
if (convertView == null) {
// Create new View
thumbnail = new ImageView(mContext);
} else {
// Recycle View
thumbnail = (ImageView) convertView;
}
// Set the placeholder Drawable
thumbnail.setImageResource(R.drawable.placeholder);
if (position < amountOfPhotos) {
if (album.getLinks().size() >= 1) {
// imageFetcher creates an AsyncTask for every call of loadThumbnail(...)
imageFetcher.loadThumbnail(thumbnail, album.getURL(position));
}
// Formating the ImageView a little...
...
}
return thumbnail;
The problem is the performance of the AsyncTasks. The ListView displays on my phone around 7 items resulting in roughly a dozen simultaneous AsyncTasks when the user scrolls through the list. This is fine, the list still builds quickly.
Now the album's GridView displays 15 items at once and scrolling through the List creates many many AsyncTasks. Since some AsyncTasks have to download from the web, they stay alive for a couple of seconds. This completely slows out the AsyncTasks, which would only have to reload Bitmaps from cache.
The result is, that as long as many AsyncTasks are running, the GridView does not display images when scrolling back up, even if it just displayed them a few seconds ago. Simply because there are too many AsyncTasks.
Any suggestions on how to solve my problem? I was thinking of something like an AsyncTask factory, where I can queue jobs and set priorities. That way I could control which job is executed next and I could also control the maximum amount of AsyncTasks running at once.
It would already help me if someone could tell me if my approach sounds right in general, or if I'm completely on the wrong track with AsyncTasks here...
I suggest you to use a library like Picasso that's make all of your placeholder/async image downloading very, very easy !
Starting from Android 3.0 AsyncTasks are executed sequentially. So it's totally possible that some long-running AsyncTasks will block others. In order to execute the concurrently you can use asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
But generally a good solution will be to create a ThreadPoolExecutor and execute your Runnables on it. This way you can control the number of tasks running and decide which order to use.
As the title says, I'm using a BaseAdapter to display items in a ListView. Obviously a ListView will reuse views, including TextViews and NetworkImageViews.
Assuming 3 items can be displayed at once, the NetworkImageView will be reused for items at index: 1, 4, 7, ....
Depending on what's being displayed, the NetworkImageView will either:
request the image from the Network and display it,
display a cached Bitmap,
or display a local drawable resource.
Items 2 and 3 work fine, however in Scenario 1, let's say we're displaying item at index 4 from the network, and the user scrolls to item 7 before 4 is loaded and it's a local resource, we display the local resource. However our network image request may just be finishing now, so we end up displaying an incorrect image.
How can I enforce the proper (expected)behavior?
The answer from #Snicolas is spot on, but lacks some pointers on how to actually accomplish that. So here goes.
The general idea is to keep track of the ongoing image requests for every row. That way, when you encounter a recycled row, you can cancel the pending request and kick off a new one for the new data relevant to that row.
One straightforward way to accomplish that is to make the ImageContainer that you can get back when requesting an image load, part of the adapter's ViewHolder/RowWrapper. If you're not using this pattern yet, you should. Plenty of examples out there, including a good I/O talk.
Once you've added the ImageContainer to your holder, make an image request and store the container that you get back. Somewhat like this:
ImageListener listener = ImageLoader.getImageListener(holder.imageview, defaultImageResId, errorImageResId);
holder.mImageContainer = ImageLoader.get(url, listener);
The next time a recycled row comes in the adapter's getView() method, you can get your holder back from it and check wether it has a ImageContainer set. One of the following 3 scenarios may apply:
There is no ImageContainer, which means you're good to go to make a new image request.
There is an ImageContainer and the url that it is loading is the same as for the new row data. In this case you don't have to do anything, since it's already loading the image you're after.
There is an ImageContainer but the url that it is loading is different from the new row data. In this case, cancel the request and make a new one for the current row data.
If you like, you can move some of this logic by having your BaseAdapter extension implement AbsListView.RecyclerListener (and set the adapter as recycler listener for the ListView or GridView). The onMovedToScrapHeap(View view) method gets passed in the view that has just been recycled, which means you can cancel any pending image requests in there.
You don't need to enforce anything if you use the provided NetworkImageView.
NetworkImageView detects when it has been recycled and cancels the request automatically.
I don't know this API but my guess is that you should cancel any pending request before recycling such a view. How you can do that I can't say.
Did you here of alternatives like :
picasso ?
robospice UI Module ?