I am trying to use android Room API to load records from sQlite in pages.
The issue is Paging library is loading entire database into model class and binding it with the adapter which is making UI thread skip frames. It suppose to load 20 records and then keep on adding more when required
This is my view model class
public class UserViewModel extends ViewModel {
public LiveData<PagedList<User>> userList;
public UserViewModel() {
}
public void init(UserDao userDao) {
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(10)
.setPageSize(20).build();
userList = (new LivePagedListBuilder(userDao.usersByFirstName(),
pagedListConfig))
.build();
}
}
Paged adapter
public class UserAdapter extends PagedListAdapter<User, UserAdapter.UserItemViewHolder> {
protected UserAdapter() {
super(User.DIFF_CALLBACK);
}
#Override
public UserItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.item_user_list, parent, false);
return new UserItemViewHolder(view);
}
#Override
public void onBindViewHolder(UserItemViewHolder holder, int position) {
User user= getItem(position);
if(user!=null) {
holder.bindTo(user);
}
}
static class UserItemViewHolder extends RecyclerView.ViewHolder {
TextView userName, userId;
public UserItemViewHolder(View itemView) {
super(itemView);
userId = itemView.findViewById(R.id.userId);
userName = itemView.findViewById(R.id.userName);
}
public void bindTo(User user) {
userName.setText(user.firstName);
userId.setText(String.valueOf(user.userId));
}
}
}
Binding with recycler View:
UserViewModel viewModel =
ViewModelProviders
.of(this)
.get(UserViewModel.class);
viewModel.init(userDao);
final UserAdapter userUserAdapter = new UserAdapter();
viewModel.userList.observe(this, pagedList -> {
Toast.makeText(this, "Page " + pagedList.size(), Toast.LENGTH_SHORT).show();
Log.e("Paging ", "Page " + pagedList.size());
userUserAdapter.setList(pagedList);
});
recyclerView.setAdapter(userUserAdapter);
02-18 10:19:40.409 15310-15310/com.androidkt.archpaging E/Paging: Page
200
Any idea what I am missing.
By the paging implementation, your result count should indeed be the full size of the query (200), as you configured to do so, the RecyclerView will receive placeholders null for the views which data is not ready yet. This is intended for when you want to show the whole list views but only bind the content of it when the data is available. But your RecyclerView should not call onCreateViewHolder and onBindViewHolder for the entire count unless it is visible.
Check (put a breakpoint) the method onMeasure or onLayout on your RecyclerView to see if the method is not returning a bigger height than expected (probably the expected is something around the size of your screen). Sometimes the actual height of RecyclerView is much bigger than the screen and the adapter call onBindViewHolder() for the total number of items because it's "visible" to it instead of the number we can see. This would trigger the DataSource to query the database and bind the views before you want.
try userUserAdapter.setList(pagedList); put out observe . observe use listner list change . You . You need to initialize the list and set up recyclerview normally .
The object list should be included in the pageAdapter in the usual way
You should call userUserAdapter.submitList(pagedList).
setList() is used for RecyclerView.Adapter not for PagedListAdapter.
Related
I have a layout with this hierarchy
< NestedScrollView fillViewPort=true>
< LinearLayout>
< Viewgroup/>
< ViewGroup/>
< RecyclerView/>
< ViewGroup/>
< /LinearLayout>
< /NestedScrollView>
Sometimes i need to update my recyclerview elements, but it freeze main thread.
My guess its because scrollview need to measure it all again.
I really like to know how i should do this?
Replace recyclier view with layoutinlfate?
Recyclerview with height fixed?
Replace nestedscrollview, with recyclview? will have recyclview inside reclyerview. This works?
This is a common UI pattern and android:nestedScrollingEnabled="true" is not a fix for this.
Best approach to this pattern is to use a single recyclerview with multiple view types instead of having nested elements. The result is a more complicated recyclerview but you have better performance and more control with it.
Try to wrap current layout with tag like below:
< RelativeLayout> // math_parent
< NestedScrollView fillViewPort=true>
< LinearLayout>
< Viewgroup/>
< ViewGroup/>
< RecyclerView/>
< ViewGroup/>
< /LinearLayout>
< /NestedScrollView>
< /RelativeLayout>
It will prevent scrollview and recyclerview measure again.
First of all, based on Android documentation:
Never add a RecyclerView or ListView to a scroll view. Doing so
results in poor user interface performance and poor user experience.
Updating UI is always done on the main thread. So when you want to update the RecyclerView it affects performance. For a better user experience, you can do your calculation in another thread and update UI in the main thread. in bellow code, I simulate your situation:
activity_main.xml
<androidx.constraintlayout.widget.ConstraintLayout
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="700dp"
android:background="#color/black"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
in my example, updating the list occurs in every second. here is the code:
public class MainActivity extends AppCompatActivity {
private MyAdapter myAdapter;
int cnt = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myAdapter = new MyAdapter();
RecyclerView recyclerView = findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(myAdapter);
AsyncTask.execute(() -> updateList());
}
private void updateList() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
#Override
public void run() {
cnt++;
myAdapter.addToList(cnt + "");
}
}, 1, 1000);//Update List every second
}
}
list is updated via updateList() method. for running this in new thread I use AsyncTask :
AsyncTask.execute(() -> updateList());
and finally here is my adapter:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
List<String> list = new ArrayList<>();
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false);
return new MyViewHolder(itemView);
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder holder, int position) {
holder.textView.setText(list.get(position));
}
#Override
public int getItemCount() {
return list.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.text_view);
}
}
public void addToList(String s) {
list.add(s);
new Handler(Looper.getMainLooper()).post(() -> {
notifyItemInserted(list.size());
});
}
}
As can be seen notifyItemInserted is called in Handler for updating UI on the main thread. for notifying recycler in different situations you can refer to https://stackoverflow.com/a/48959184/12500284.
You can create multiple adapters for all the views in your nestedscrollview and then concat them using concat adapter. In my case, my nestedscrollview had one header and one recyclerview. So I created one adapter for the header and one for the recycler view. You can use lambda or an interface for communicating between header adapter and activity. Here's the code:
val listAdapter = ManageQuoteAdapter(this)
val headerAdapter = ManageQuoteHeaderAdapter(handler)
val concatAdapter = ConcatAdapter(headerAdapter, listAdapter)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = concatAdapter
Pros:
Better performance, specially if your recyclerview has lots of images. Earlier all the images used to load at once, ultimately throwing a memory exception
Best suited for simpler views that has a header, footer and list of items.
Cons:
You need to create at least one more adapter file. So more file to maintain.
Complexity will increase if you have so many actions, input fields etc in header or footer part (Example - Forms). In this case, NestedScrollView will be more suitable as anyways recyclerview will have fewer items.
Here's how my screen looks:
when you are using vertical recycler view inside vertical NestedScrollView without using a constant height, RecyclerView height will be wrap_content even when you set the RecylcerView height to match_parent. so when you update your RecyclerView data, MainThread will be frozen because all the RecyclerView items onBind() method will be called. This means the RecyclerView doesn't recycle anything during scroll! if you can not use the MultipleViewType for your design, try to set a constant height to the RecyclerView.
This solution makes some difficulties for you. such as handling scrollablity.
The most likely reason for a UI freeze is that you're retrieving the data from some slow source, like the network or a database.
If that is the case, the solution is to retrieve the data on a background thread. These days I'd recommend using Kotlin Coroutines and Kotlin Flow.
Your DB would return a Flow<List>, the contents of which will automatically be updated whenever the data in the DB changes, and you can observe the data updates in the ViewModel.
Get a Flow object from your database:
#Dao
interface MyDataDao {
#Query("SELECT * FROM mydata")
fun flowAll(): Flow<List<MyDataEntity>>
}
The Fragment will observe LiveData and update the list adapter when it changes:
class MyScreenFragment(): Fragment() {
override fun onCreate() {
viewModel.myDataListItems.observe(viewLifecycleOwner) { listItems ->
myListAdapter.setItems(listItems)
myListAdapter.notifyDataSetChanged()
}
}
}
In the ViewModel:
#ExperimentalCoroutinesApi
class MyScreenViewModel(
val myApi: MyApi,
val myDataDao: MyDataDao
) : ViewModel() {
val myDataListItems = MutableLiveData<List<MyDataListItem>>()
init {
// launch a coroutine in the background to retrieve our data
viewModelScope.launch(context = Dispatchers.IO) {
// trigger the loading of data from the network
// which you should then save into the database,
// and that will trigger the Flow to update
myApi.fetchMyDataAndSaveToDB()
collectMyData()
}
}
/** begin collecting the flow of data */
private fun collectMyData() {
flowMyData.collect { myData ->
// update the UI by posting the list items to a LiveData
myDataListItems.postValue(myData.asListItems())
}
}
/** retrieve a flow of the data from database */
private fun flowMyData(): Flow<List<MyData>> {
val roomItems = myDataDao.flowAll()
return roomItems.map { it.map(MyDataEntity::toDomain) }
}
/** convert the data into list items */
private fun MyData.asListItems(): MyDataListItem {
return this.map { MyDataListItem(it) }
}
}
In case you didn't know how to define the objects for in a Room Database, I'll give you this as a hint:
// your data representation in the DB, this is a Room entity
#Entity(tableName = "mydata")
class MyDataEntity(
#PrimaryKey
val id: Int,
val date: String,
// ...
)
// Domain representation of your data
data class MyData(
val id: Int,
val date: SomeDateType,
// ...
)
// Map your DB entity to your Domain representation
fun MyDataEntity.toDomain(): MyData {
return MyData(
id = id,
date = SomeDateFormatter.format(date),
// ...
)
}
In my app I have two LiveData objects, one for getting items from 0-10 and second to get the items from 11-20. I'm trying to load the data in a RecyclerView but instead of having 20 items, the first 10 (0-10) are replaces with new 10 (11-20). This is what I have tried:
recyclerView = findViewById(R.id.recycler_view);
adapter = new ItemsAdapter();
recyclerView.setAdapter(adapter);
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
To get items from 0-10 I use this method:
private void getInitialItems() {
ItemListLiveData liveData = viewModel.getItemsLiveData();
liveData.observe(this, itemtList -> adapter.submitList(itemtList));
}
To get items from 11-20 I use this method:
private void getNextlItems() {
ItemListLiveData liveData = viewModel.getItemsLiveData();
liveData.observe(this, itemtList -> adapter.submitList(itemtList));
}
This is my ViewModel class:
public class ItemListViewModel extends ViewModel {
private ItemListRepository repository = new ItemListRepository();
ItemListLiveData getItemsLiveData() {
return repository.getItemListLiveData();
}
}
In the repository I only get the items from a back-end server. This is my adapter class:
public class ItemsAdapter extends ListAdapter<Item, ItemsAdapter.ItemViewHolder> {
ItemsAdapter() {
super(diffCallback);
}
#NonNull
#Override
public ItemViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
//Inflate the view
}
#Override
public void onBindViewHolder(#NonNull final ItemViewHolder holder, int position) {
//Bind the Item according to position
}
private static DiffUtil.ItemCallback<Item> diffCallback = new DiffUtil.ItemCallback<Item>() {
#Override
public boolean areItemsTheSame(#NonNull Item oldItem, #NonNull Item newItem) {
return oldItem.id.equals(newItem.id);
}
#Override
public boolean areContentsTheSame(#NonNull Item oldItem, #NonNull Item newItem) {
return oldItem.equals(newItem);
}
};
}
My expectation is when using DiffUtil.ItemCallback to get both lists as a cumulative list since all the objects are different. Even if I pass both lists to the same adapter, I end up having only ten items (11-20). How to use submit list so I can have 20 items in my list and not only 10 (11-20)?
DiffUtil.ItemCallback is used for animating smoothly changes in dataset in adapter.
For example if you have have 10 items, than submit list with 9 items that were contained in previous 10, DiffUtil.ItemCallback will determine difference between old and new list, which position that element was and animate changes accordingly. What you are looking for in your case is Pagination where you can expand/show items while scrolling.
You don't need two LiveData for this one, you cast fetch data from some source add it to LiveData of Pagination. First it will be showed 10 items, then if you scroll to end another 10, and so on. You can adjust type of pagination by your needs with provided Configuration.
To do all that without Pagination.
liveData.observe(this, itemtList -> adapter.submitList(adapter.getCurrentList().addAll(itemtList)));
Get previous data, on top of that data add new data and it will all be shown.
I have created a recyclerview for displaying data which is fetching from the server. I had used only single layout for displaying the data.
Now my requirement is like when I upload images or videos, then the uploading status should be displayed on top of the data which is displaying from the server. i-e on 0th position of recyclerview. I can add any number of images or videos.
after image or video successfully upload i also want to remove that row from recyclerview. I thought of doing using getItemViewType(). In this using two layout. I don't know this method is correct or not. I am not getting any solution to this,
Please.....
any help...
You can easily achieve that using the library SectionedRecyclerViewAdapter.
You should first create a section class:
class MySection extends StatelessSection {
String title;
List<String> list;
public MySection(String title, List<String> list) {
// call constructor with layout resources for this Section header, footer and items
super(R.layout.section_header, R.layout.section_footer, R.layout.section_item);
this.title = title;
this.list = list;
}
#Override
public int getContentItemsTotal() {
return list.size(); // number of items of this section
}
#Override
public RecyclerView.ViewHolder getItemViewHolder(View view) {
// return a custom instance of ViewHolder for the items of this section
return new MyItemViewHolder(view);
}
#Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
// bind your view here
itemHolder.tvItem.setText(list.get(position));
}
#Override
public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
return new SimpleHeaderViewHolder(view);
}
#Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;
// bind your header view here
headerHolder.tvItem.setText(title);
}
}
Then you set up the RecyclerView with your Sections:
// Create an instance of SectionedRecyclerViewAdapter
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
MySection uploadsSection = new MySection("Uploads", uploadList);
MySection downloadsSection = new MySection("Downloads", downloadList);
// Add your Sections
sectionAdapter.addSection(uploadsSection);
sectionAdapter.addSection(downloadsSection);
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
This way you manage the items from your upload and download lists separately by removing them from uploadList/downloadList then notifying the changes to the adapter. The upload items will always be displayed at the top of the RecyclerView because they are in the first section added to the adapter.
If you have different layouts for uploads and downloads you can create a different Section class for each.
Make two viewTypes like:
private static final int REGULAR_HOLDER = 1;
private static final int LOADING_HOLDER = 2;
Override getItemViewType and return LOADING_HOLDER for position 0, and REGULAR for all others. Also have state if you are loading or not. If you are not loading anything you will return REGULAR_HOLDER for all rows (positions).
Then in onCreate check if you have REGULAR or LOADING viewType, and create proper Holder. Important: make your Adapter implement RecyclerView.Adapter<VH> not your custom implementation of ViewHolder.
onBind executes next. There you will have to check if your viewHolder object you get is instance of RegularViewHolder or instance of LoadingViewHolder, like:
if (holder instance of RegularViewHolder) {
holder.doStuff();
} else if (holder instance of LoadingViewHolder) {
holder.showLoading();
}
Now, before this you should made two layouts. One is for your regular rows, and other is for row that will show loading. Make two classes that implement ViewHolder, in example above i called them RegularViewHolder and LoadingViewHolder.
EDIT: few things to keep in mind. I told you to keep a loading state (loading or not loading), so if you want to remove LOADING row, you could make that change and call notifyDataSetChanged();. Now, getItemViewType should return all REGULAR rows if you did it right.
Also you should keep in mind that if you want to show 10 rows of your data. Your getItemCount() should return 11 (10 + loading row) if there is loading happening. Also, in that case your data rows start from second row (position 1).
I'm using RecyclerView with Xamarin.Android. I'm filling the adapter with a List<Article>. For adding more items to list when user scrolls down I'm using RecyclerView.OnScrollListener. It triggers an even and inside the event handler I'm retrieving the data and adding it to the old list with and then call adapter.NotifyDataSetChanged ();. It retrieves the data and triggers the event but doesn't update the recyclerview on the device.
What am I doing wrong??
RecyclerView recyclerview;
NewsAdapter adapter;
private List<Article> listofarticles;
var onScrollListener = new XamarinRecyclerViewOnScrollListener (layoutmanager);
onScrollListener.LoadMoreEvent += async (object sender, EventArgs e) => {
//Load more stuff here
Console.WriteLine ("*** loading more stuff ***");
var newarticles = await GetData ("2");
listofarticles = listofarticles.Concat (newarticles).ToList ();
adapter.NotifyDataSetChanged ();
};
Here's my adapter: (The clickhandler and itemcount methods are omitted)
// Adapter
public class NewsAdapter : RecyclerView.Adapter
{
// Event handler for item clicks:
public event EventHandler<int> ItemClick;
private Context globalContext = null;
// Underlying data set:
public List<Article> _listofarticles;
// Load the adapter with the data set (articles) at construction time:
public NewsAdapter (List<Article> listofarticle, Context context)
{
this._listofarticles = listofarticle;
globalContext = context;
}
// Create a new article CardView (invoked by the layout manager):
public override RecyclerView.ViewHolder OnCreateViewHolder (ViewGroup parent, int viewType)
{
// Inflate the CardView for the photo:
View itemView = LayoutInflater.From (parent.Context).Inflate (Resource.Layout.latestnews_recycler_article, parent, false);
// Create a ViewHolder to find and hold these view references, and
// register OnClick with the view holder:
NewsViewHolder vh = new NewsViewHolder (itemView, OnClick);
return vh;
}
// Fill in the contents of the article card (invoked by the layout manager):
public override void OnBindViewHolder (RecyclerView.ViewHolder holder, int position)
{
NewsViewHolder vh = holder as NewsViewHolder;
// Set the ImageView and TextView in this ViewHolder's CardView
// from this position in the photo album:
//vh.Thumbnail.SetImageResource (_listofarticles[position].Thumbnail);
// We use Picasso to load images efficiently
Picasso.With (globalContext).Load (_listofarticles[position].Thumbnail)
//.Placeholder(R.drawable.ic_placeholder) // optional
//.Error(R.drawable.ic_error_fallback) // optional
//.Resize(250, 200) // optional
//.Rotate(90) // optional
.Into (vh.Thumbnail);
vh.Title.Text = _listofarticles[position].Title;
}
}
I needed to add new items to the list inside adapter (not the one for the activity class) that is _listofarticles and then call NotifyDataSetChanged inside the adapter class like this.NotifyDataSetChanged ();. So to achieve that I added a method called AddToList to the adapter:
public void AddToList(List<Article> newitemslist)
{
_listofarticles = _listofarticles.Concat (newitemslist).ToList ();
this.NotifyDataSetChanged ();
}
and I call it inside the event handler for scrolling like:
var newarticles = await GetData ("2");
adapter.AddToList (newarticles);
This properly updates the adapter and adds items to the end of recyclerview.
In Xamarin because of memory management and garbage collection when using an ArrayAdapter, the implementation makes a copy of your backing list when you create it.
In order to be able to use the NotifyDataSetChanged, you need to use the Android.Runtime.JavaList instead of the .Net IList.
Here is a snippet :
ArrayAdapter adapter;
Android.Runtime.JavaList<string> messages = new Android.Runtime.JavaList<string>();
Then either on your OnCreate or in the OnResume, add the following code :
adapter = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, messages);
listView.Adapter = adapter;
You can now add items to your list and call the NotifyDataSetChanged and you should be able to get the changes.
In my case, i have a click handler on the button that updates the listview :
void BtnSend_Click(object sender, System.EventArgs e)
{
var message = editText.Text;
messages.Add(message);
adapter.NotifyDataSetChanged();
}
I have a simple adapter
public class ConversationListAdapter extends
RecyclerView.Adapter<Conversation.ViewHolder> {
private List<Conversation> items;
private Activity activity;
public ConversationListAdapter(Activity activity, List<Conversation> items) {
super();
this. items = items;
this.activity = activity;
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
Conversation conversation = mItems.get(i);
viewHolder.name.setText(conversation.getName());
if ( conversation.getUrl() != null) {
Picasso.with(activity.getApplicationContext()).load(conversation.getUrl())
.into(viewHolder.imageView);
}
}
and a basic
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {..}
}
Now in the fragment as always:
mRecyclerView.setAdapter(new ConversationAdapter(getActivity(), mItems));
Now Im calling my rest api to get the data and the first time it works great everything is where it should be (let's say in c there is only 2 items and the order is conv1 and conv2)
private void handleResult(List<Conversation> c) {
items.clear()
items.addAll(c)
adapter.notifyDataSetChanged()
}
But... now if I refresh for example and the data in the List comes in a different order (conv2 and then conv1) after the adapter.notifyDataSetChanged() both of my imageView in the list have the same pictures.. ! But the textView however has the right text
This only happens with view filled with Picasso and cannot understand why
Could someone help me on this ?
you use if condition in any adapter you also have to set else part of it. i also don't know exactly why this is happen that if condition is given it takes same condition for child which are not match this, may be a bug in android. please try else part of it also. may be this work for you.
You have to replace your items in your adapter or create a new adapter with the new items
1st solution:
private void handleResult(List<Conversation> c) {
mRecyclerView.setAdapter(new ConversationAdapter(getActivity(), c));
}
2nd solution:
private void handleResult(List<Conversation> c) {
adapter.setList(c);
adapter.notifyDataSetChanged();
}
And don't to forget to create setList(List<Conversation> c) method in your Adapter