I'm pretty new to Android and I'm finding it difficult to figure out the correct/best way to accomplish the following:
I'm using ActionBarSherlock to have a set of tabs that each load a fragment. One of these is a ListFragment (actually SherlockListFragment). I started off by simply using a new thread to load the list, and all seemed fine, until you switch to a different tab before the list has loaded. Then it fails at SetListShown(true) because I guess the fragment is not active.
public override void OnActivityCreated(Bundle savedInstanceState)
{
base.OnActivityCreated(savedInstanceState);
SetListShown(false);
var listView = (ListView) Activity.FindViewById(Android.Resource.Id.List);
Button loadMore = new Button(Activity);
loadMore.SetText("Load more articles", TextView.BufferType.Normal);
loadMore.Click += LoadMoreArticles;
listView.AddFooterView(loadMore);
ThreadPool.QueueUserWorkItem((obj) => {
var articles = _articleService.GetArticles(0, 10, DateTime.UtcNow);
_items = articles.Select(x => new ArticleSummaryModel(x)).ToList();
Activity.RunOnUiThread(() => {
_adapter = new ArticleList_Adapter(Activity, _items);
ListAdapter = _adapter;
SetListShown(true);
});
});
}
What I'd like to happen is that when a tab is opened for the first time it starts loading the data, showing the standard progress indicator, then then it completes the list is shown and the progress indicator removed. If the tab is change while data is loading, it should be visible when that tab is opened again.
If there's a simple way to achieve this that I've missed, great! It it's simply not possible with Android, I'd like to know that too so I can stop trying ;-)
I've read a lot about AsyncTask, AsyncTaskLoader, IntentService, etc. but I'm not at all clear what I need to use, and it any of these would actually achieve what I want!
FYI I'm using mono for android and the compatibility pack to support v2.3+, but can hopefully translate any java examples etc.
Many thanks in advance!
Robin
I'm not sure how this would work with monodroid but I'm guessing it should be about the same. Use an AsyncTaskLoader since they are tied to the activity lifecycle through the LoaderManager. You should have your fragments implement the LoaderCallbacks interface and call loaderManager.init(...) somewhere in there (for example, in onActivityCreated). When the loader task is finished you'll be notified in onLoadFinished with the data and there you can load your it into the adapter and do the progressbar and listview transition. You can find examples on how to implement an AsyncTaskLoader on the documentation page here or with a bit more detail here. As for the transition, use something like this which gives some polish to your app.
Related
My Android app has this flow of screens when launched:
Splash -> Chat Groups > Chat Screen (showing chat messages)
On Chat Screen I have my Custom RecyclerView Implemented.
On fresh launch (or after killing the app), I go to Chat Screen, it loads previous messages fine, and new incoming message is also seen when u r on this screen.
Now if I press Android's Back button few time to exit the app, and then relaunch the app and go to Chat Screen, previous messages appear fine BUT the new incoming message is not visible.
Important thing is, even if I don't go to Chat Screen the first time and close the app from Groups Screen, then relaunching and going to Chat Screen again causes the problem and I dont see new incoming Chat messages.
I have debugged it and all code is being executed fine. The incoming message is added to the list of RecycleView, and notifyDataSetChange() is being called, but onBindViewHolder() is not being called in this case, and that's why the list doesn't get updated.
The code is pretty lengthy, but if u still need to see it then I'll try to add.
This is driving me crazy, I am pretty sure it's a bug in Android.
If u can propose a workaround, like clearing the RecyclerView or Adapter somehow that it gets to same state as when i Kill the app and launch..
Here is the code:
//Initialize Recycler view
mMessageRecycler = findViewById(R.id.recyclerview_message_list)
mMessageRecycler?.layoutManager = LinearLayoutManager(this)
....
if (messagesAdapter == null) {
messagesAdapter = NewMessageListAdapter(this)
mMessageRecycler?.adapter = messagesAdapter
}
//Adapter
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position:Int) {
val message = messageList[position]
(holder as ReceivedMessageHolder).bind(message)
}
override fun getItemCount(): Int {
return messageList.size
}
...
//add new chat message. Breakpoint does hit this code
messageList.add(newMessage)
notifyDataSetChanged()
As it's impossible to tell what is going on without looking at a bigger picture, I'll give you a few pointers.
a. I suggest you attempt to use ListAdapter<T, K> as it forces you to provide a DiffUtil.ItemCallback implementation. This will allow you to avoid calling the expensive and extremely inefficient notifyDataSetChanged(); instead you will call adapter.submitList(...) and supply a List<T> with your data.
K is the ViewHolder type. Usually you use RecyclerView.ViewHolder (if I correctly recall or if you only have one viewHolder type then you can couple that there and use it directly. Otherwise you'll just have to "cast" your ViewHolders to be able to call their "bind" method.
b. As for "it doesn't work when I get back", this is a bit harder to detect, as we haven't seen how/where/when you fetch this data; are you using Android Coroutines? Are the list of messages stored in a repository relying on memory or database persistence? who updates this list?
As you can see, there are a few outstanding questions that we (the readers) cannot possibly infer given the information you've provided.
If you want to see the simplest example of a RecyclerView with ListAdapter, I often tend to link this sample I wrote because it shows how to put all pieces together.
c. You shouldn't need to do if (adapter == null) { // create it and set it } either. You can have:
class YourActivityOrFragment : ... {
private val adapter = YourAdapter()
override fun onViewCreated(...) {
yourRecyclerView.adapter = adapter
}
You can later set the data in the adapter once you have it, there's no need to delay the creation of this. If you're going to use a LinearLayoutManager, remember you can also set it directly in XML and avoid writing the code.
Finally I found the problem! It was due to some memory leak and threads issue.
At some point in the code i was re-initializing my Mqtt class, without checking if it is not null. So I just added a null check and it fixed
if (mqttMy == null) // added
mqttMy = MqttMy(context)
I'm new to all of this and was making good progress but I have hit a brick wall with working out how to do the next thing. I am using Kotlin and have a Fragment with an associated Recyclerview Adapter. I would like to set OnClick (On or Off) against items in a row depending upon a value in the Fragment which could change at any time.
My adapter works fine to show and update the array of data and also to implement OnClick.
I have tried sending a data element via a constructor which changed in the fragment but always showed as the initial setting in the adapter. The same with trying to call a method.
Many other questions touch on the issue but only show snippets of code, and it seems that I'm not advanced enough to get them working successfully in my code.
Could anyone please provide a pointer to a working set of Kotlin code that includes parsing a variable from fragment to adapter - perhaps in Git or a tutorial. I'm sure that if I can study a working program I can move forward. Thank you.
It would have been better if you had included your Adapter and Fragment code in the question, that would have helped us in understanding how you have setup everything and what data model are you passing to adapter.
But looking at your question, one solution that comes to my mind is to add an enabled boolean in your data model that is displayed in the ViewHolder. Using this you can set view.clickable = model.enabled. Now whenever your "value in the Fragment" changes you can update this list and let the adapter rebind items.
Note that the above solution is when you want to selectively enable/disable clicks on individual items. If you want to do this for all items at once, it's better to create a variable in adapter that you can change from the Fragment, and inside the clickListener you can check the value of that adapter variable. If it's false, just return out of the click listener. Something like,
view.setOnClickListener {
if(adapterValue) {
// handle Click
}
}
If this approach doesn't help, I would ask you to add more context in your question and show what you have done so far.
Screen 1: GridView
Screen 2: Detail Page
Task Achieve:
1) Load all the videos in gridview from the server.
2) User clicks at any position of gridview item.
3) Open and play the particular video in detail screen.
4) On vertical scroll play next or previous videos.
Current Implementation:
GridFragment {
ArrayList<VideoPostModel> videoPostList;
RecyclerView gridView;
onnItemClick() {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("data", videoPostList);
intent.putExtra("click_index", clickedIndex);
intent.putExtra("pagination_index", paginationIndex);
startActivity(intent);
}
}
DetailActivity {
VerticlaViewPager vertiCalViewPager;
ArrayList<VideoPostModel> videoPostList;
onCreate() {
videoPostList = getIntent().getParcelableArrayListExtra("data");
clickedIndex = getIntent().getIntExtra("clickindex", 0);
paginationIndex = getIntent().getIntExtra("pagination_index", 0);
VideoFragmentStatePagerAdapter viewPagerAdapter = new VideoFragmentStatePagerAdapter(videoPostList);
vertiCalViewPager.setAdapter(viewPagerAdapter);
}
}
Problem:
If videoPostList has more data(approx 100+ objects of VideoPostModel) while passing data from fragment to activity then app crashes, as there is a limitation of sending data with intent(https://stackoverflow.com/a/37054839/3598052).
Hacky Alternatives:
1) Static arraylist
2) Arraylist in Application class
Looking for the OPTIMAL and EFFICIENT solution to achieve above functionality.
Any suggestion, reference link or code in the direction of achieving this would be highly appreciated, and thanks in advance.
Update 1:
Another solution I found is passing data with enum, but as per comments I'm not sure about it's performance. Refrence: https://stackoverflow.com/a/14706456/3598052
I think you can write in an activity or use Arraylist in the application as you mentioned. Or it could be a library that recently appeared in the Android Jetpack. It is similar in nature to the Arraylist in application.
ViewModel objects that make it easier to manage and store data.
It lets you access data at different activities or fragments in an application. You try it and hope it will be useful to you
You have several options. I'll put my opinion here.
Static list
Enum
Singleton class with list
LiveData
Most easy would be making static list at activity or application level. Just make sure you are freeing up List memory after use by making it NULL.
Another solution I found is passing data with enum, but as per
comments I'm not sure about it's performance
There would be sure some differences in each of above approaches. But that would not be measurable difference, because each approach put List in memory, use it and then free up.
Looking for the OPTIMAL and EFFICIENT solution to achieve above
functionality.
Make static List, and make it NULL after use. will be most efficient and easy way.
You can make List NULL in onDestroy() of your Fragment.
You can use LiveData but I think it would be not a good idea to add LiveData library just for one use in app. Also you need to understand it first. So you can go with static list.
in Activity
showFragment();
ApplicationClass.list = myList;
In Fragment
onViewCreated(){
...
setAdapter(ApplicationClass.list);
...
}
onDestroy(){
ApplicationClass.list = null;
}
Important
It is never a good idea to pull all data at once from server. Please do pagination, which you app needs most, because there can be thousands of users online at one time.
So by that approach you will pass only few items to Fragment. then you will do pagination in Fragment.
This approach needs time to change flow a bit. But is most robust way in your case.
Edit
If you are already using pagination and still getting large data at one time, that's again an issue. Because pagination is used to escape these memory issues.
You can do 2 things as solution.
1. Ask for limited data at once, say 50-60 items per request.
2. You can map and remove unnecessary fields from your list when passing in intent.
I would prefer the first one.
I know I'm late but this will help some future visitor.
Add Pagination & transfer data with arraylist & clicked position to detail activity using intent & after, set the current position of clicked position.
like viewpager.setCurrentPosition(clickedPosition);
after some advice really. My app fills a list view on load using a mediastore cursor. This is pulling music linked to user defined folder, which in most cases will be all of their stored music. I have one beta tester that is using an Archos Tablet with approximately 10000 songs on it, running android 2.2. While performance for most users is pretty slick, I wanted to improve the experience for users such as this.
The current process:
User loads app.
App finds default folder
App populates list view with music within and below that folder
User moves to a folder further down the tree, list view is repopulated based on the selected folder
User moves again....list is repopulated based on the selected folder...
So what I'm wondering is this - is it faster/more efficient to use the following process:
User loads app
App finds default folder
app populates list view with music within and below that folder
user moves to a folder within the tree, THE LIST IS FILTERED TO THAT FOLDER
if the user moves higher up the tree than the default data (i.e. potential for new files), the list view is repopulated, but only in this circumstance.
So basically,my questions is "how does filtering compare to repopulation?"
A very good question. Let me try to answer this.
Filtering is actually repopulation the ListView, whereas you create/get a new collection and tell the Adapter it's content has changed by calling notifyDataSetChanged.
The 'heavy' work for a listView is that getView call in it's adapter. I've tested this myself, and if you inflate a new View every time getView is called, the performance drops. Heavenly.
The ListView's adapter is built so that already inflated views can be re-used, which tackles above named problem. Besides, only visible views are loaded, so it's not like the Adapter is going to create 10000 views if you tell it's collection is 10000 items big.
notifyDataSetChanged will tell the adapter to rebuild the listviews content, but it still contains previously inflated views. So here is a big performance win.
So my advice for you is, when you are using the same 'row layout' to just repopulate the ListView using notifyDataSetChanged. I've implemented this multiple times myself without noticing any UI performance issues. Just make sure to do the filtering of your collection an a background thread. (AsyncTask comes in handy here).
One last tip: Do you have any phone thats quite old? Or someone you know does? Find the slowest phone you can and test your application on it for performance. I have a HTC Legend myself, which is outdated and slow if f*ck, but perfect for performance testing. If it runs on my (old) phone, it runs on any phone.
Pseudo code sample if your applications flow:
public class FolderListActivity extends Activity implements OnItemSelected {
// NOTE: THIS IS PSEUDO CODE
private ListView listView
private Adapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstaceState);
// setContentView here
listView = (ListView)findViewById(R.id.your_listview_here);
listView.setOnItemSelectedListener(this);
}
public class AsyncLoadMusicLocationTask extends AsyncTask<Void, Void, List<String>> {
public List<String> doInBackground(Void... params) {
// Load the information here, this happens in the background
// using that cursor, i'm not sure what kind of things you are using
// So I assumed a List of Strings
}
#Override
public void onPostExecute(List<String> result) {
// Here we have our collection that was retrieved in a background thread
// This is on the UI thread
// Create the listviews adapter here
adapter = new Adapter(result, and other parameters);
listView.setAdapter(adapter);
}
}
#Override
public void onItemSelect(Some params, not sure which) {
// THIS SHOULD BE DONE ON THE BACKGROUND THE PREVENT UI PERFORMANCE ISSUES
List<String> collection = adapter.getObjects();
for (int i = 0; i < collection.size(); i++) {
// Filter here
}
// this method will most probably not exist, so you will need to implement your own Adapter class
adapter.setObjects(collections);
adapter.notifyDataSetChanged();
}
}
I have implmented pagination and it display 5 records per page. Now suppose I am on page 3 and click 3'rd element then 3'rd element of page-1 is selected.
I am not able to figure out problem as I always create new list object while setting data.
I used below code
temp = new ArrayList();
this.someListAdapter = new SomeListAdapter(this, R.layout.row_facet,temp);
setListAdapter(this.someListAdapter );
Below is signature of SomeListAdapter class.
public class SomeListAdapter extends ArrayAdapter<VoNeighborhood> {
}
Please help....
There really aren't enough details here about how you do pagination with your ListView.
So I might guess you're overriding onListItemClick and using the position variable it sends you, but you then don't take into account the page you're on?
Alternatively, just don't use pagination as you have an infinite canvas to scroll your list within — I don't think I've recall seeing an Android app so far that uses pagination!