RecyclerView jumps to top when notifyDataSetChanged() called - android

I have a fragment that contains swipeRefreshLayout and infinity recyclerView in it. My fragment layout code is similar to:
<androidx.constraintlayout.widget.ConstraintLayout
.
.
.
android:layout_height="match_parent"
android:layout_width="match_parent"
android:clickable="true"
android:focusable="true"
android:focusableInTouchMode="true">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
.
.
.
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" >
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_main_news"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
I set recyclerView's adapter in onViewCreated() and call notifyDataSetChanged() in onResume(). In some devices like Samsung Galaxy A5 , when user go back to fragment and notifyDataSetChanged called, recyclerView moves to top and show first item.
How can I prevent this?
Thanks in advance.
Update: This is my onResume() code that calls notifyDataSetChanged()
override fun onResume() {
super.onResume()
if (rv.adapter != null)
(rv.adapter as MyAdapter).apply {
fontSize = Options.getFontSize()
notifyDataSetChanged()
}
}

it's expected that when notifyDataSetChanged called entire view is refreshed. When you go back to the fragment onResume is called, subsequently your notifyDataSetChanged also called. That's why you see the first item.
So, to prevent this you need to call notifyDataSetChanged when your whole data is changed or need to be updated.
I can suggest you some way.
remove notifyDataSetChanged from onResume & use diffUtil for your recyclerView data update. It will update only your changed item. Update your data only when it's necessary, like if you update any data in details & want to update in list you can override onActivityResult & notify your list there. Or you can send broadcast & receive it in you list fragment & notify you list.
thanks in advance, hope this will help.

You can try save scrolling state of the RV when updating:
val recyclerViewState = rv.layoutManager?.onSaveInstanceState()
adapter.notifyDataSetChanged()
rv?.layoutManager?.onRestoreInstanceState(recyclerViewState)

Related

Android activity wont load items at first

In my application I have empty activity and plus button which opens new activity in which I type some title and few text fields and then save. When i save that item is added to the 1. activity but not shown. I need to go to some other activity and when i come back its then loaded. Same goes for 2nd item after its added but after there are 2 or more items it always loads all the items, im not using aSyncTask, i just load data from database. Anyone knows what could be the problem?
Is there any itemView.function() that reloads all the items in a activity?
You could use LiveData for that and observe it to your Main Activity.
I assume that you are using ROOM library for your database and MVVM architecture. Here's the example from my project.
public LiveData<List<Todo>> getAllTodo() {
return allTodo;
} //This is from my ViewModel class.
and I observe it from my main activity
viewModel = new ViewModelProvider(this).get(ViewModel.class);
viewModel.getAllTodo().observe(this, new Observer<List<Todo>>() {
#Override
public void onChanged(List<Todo> todos) {
todoAdapter.setTodos(todos);
}
});
Problem was that my xml file that containted RecyclerView was set to wrap contnent so i changed
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/card_gallery"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:padding="2dp"
tools:listitem="#layout/card_item">
</androidx.recyclerview.widget.RecyclerView>
to
android:layout_width="match_parent"
android:layout_height="match_parent"

How to Using inflater to get a list view from fragment in Android Studio

I am trying to get a list view id from another xml file (fragment_friends.xml) while i am in activity_main.xml layout. I tried 2 ways, but they are not working properly.
Below code will cause the app to crash straight away because i am trying to get an id that does not exist in this layout (activity_main)
Original code
ListView friendList = (ListView) findViewById(R.id.friend_listView);
friendList.setAdapter(friendAdaptor);
Below code works, but i wont be able to insert or display any data into my database.
Try 1 code:
View inflatedView = getLayoutInflater().inflate(R.layout.fragment_friend, null);
ListView friendList = (ListView) inflatedView.findViewById(R.id.friend_listView);
friendList.setAdapter(friendAdaptor);
Below code the database and listView works, but my navbar drawer is gone and i when i try to get back to activity_main, app crashes
Try 2 code
setContentView(R.layout.fragment_friend);
ListView friendList = (ListView) inflatedView.findViewById(R.id.friend_listView);
friendList.setAdapter(friendAdaptor);
app_bar_main.xml
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<!--<include layout="#layout/content_main" />-->
<FrameLayout
android:id="#+id/flContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"/>
<android.support.design.widget.FloatingActionButton
android:id="#+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="#dimen/fab_margin"
app:srcCompat="#android:drawable/ic_dialog_email" />
</android.support.design.widget.CoordinatorLayout>
I believe the root of your problem comes down to a lack of familiarity with the general Android Fragment APIs. That's fine; everyone is new when they start. But it means that your question is quite difficult to answer concisely (beyond "read about Fragments").
You should, of course, start by reading about Fragments: https://developer.android.com/guide/components/fragments.html
Still, I think we can cover things here in brief.
All Fragments must be hosted in an Activity. The normal way to do this is either by adding a <fragment> tag to your activity's xml layout, or by including a <FrameLayout> in your activity's xml layout and then using a FragmentTransaction to dynamically add/replace/remove fragments from that frame.
In your posted code, inside app_bar_main.xml, I see this:
<FrameLayout
android:id="#+id/flContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="?attr/actionBarSize"/>
This is clearly the place where your fragments will eventually live, but you'll have to add them to the frame yourself in your Java code. You actually have a method that does this already:
private void displaySelectedScreen(int id){
Fragment fragment = ...
if(fragment != null){
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.flContent, fragment);
ft.commit();
}
...
}
It looks like this is meant to be used when your user clicks on an item in your navigation drawer, but the concept is the same. If you want your app to always start up with the "friends" fragment displayed, you can add this code to your activity's onCreate() method:
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.flContent, new friend())
.commit();
}
Now you will have a friend fragment to work with!
However, fragment transactions aren't guaranteed to be executed right away, so even though (as a user) you'll always see this friend fragment when the app starts up, you won't necessarily be able to interact with it (as a programmer) immediately after writing this code. So you'll also have to move your call to populateFriendList() from your onCreate() method.
Instead, you should populate the friends list from within the friend fragment itself! Probably the right place to do this is inside the onActivityCreated() method of your fragment.
To find a view from inside a Fragment subclass, you can't just write findViewById() the way you can in an Activity. However, you can write this instead: getView().findViewById()... as long as you're doing this after your onCreateView() method has returned.
Anyway, this answer is getting extremely long even as I try to keep it as short as possible. I hope this is enough information for you to get your app up and running. Good luck.

how to stop PullToRefresh animation of ListView

I used Android-PullToRefresh from chrisbanes to show refresh progress in my app.
I load data in AsyncTask, so I start refreshing by calling mPullRefreshListView.setRefreshing();
in onPreExecute method
and stop refreshing by
calling mPullRefreshListView.onRefreshComplete();
in onPostExecute method.
That works fine. But the problem happens when network down. Because I won`t use AsyncTask when network down any more, instead I load local cache.
I don`t want to show the PullToRefresh animations when refreshing if network down, but since it registered the setOnRefreshListener, every time when user pull down the ListView, it shows the refreshing animations, and never got disappear.
I tried to disable the animation by calling mPullRefreshListView.onRefreshComplete();, but it does`t help.
if (!Utils.isNetworkOn()) {
if (mPullRefreshListView.isRefreshing())
mPullRefreshListView.onRefreshComplete();
}
I feel very confused about this, anyone knows how to handle this?
Thanks in advance.
It is better to use SwipeRefreshLayout since Chris Bane's project is no longer maintained:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/srl_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ListView
android:id="#+id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>
In fragment:
private void pullToRefreshForConfiguredList(View view) {
mConfiguredSwipeRefreshLayout = (SwipeRefreshLayout) view
.findViewById(R.id.srl_layout);
mConfiguredSwipeRefreshLayout
.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
// call Method to be refreshed
if (mConfiguredSwipeRefreshLayout.isRefreshing())
mConfiguredSwipeRefreshLayout.setRefreshing(false);
}
});
mConfiguredSwipeRefreshLayout.setColorScheme(android.R.color.black,
R.color.red, android.R.color.black, R.color.red);
}

Refreshing fragment listview

I have a activity (Activity A) with a fragment that has a ListView inside of it and I'm calling another activity (Activity B) to add records to the fragment's list.
My problem is that calling adapter.notifyDataSetChanged on Activity A's onResume/onFragmentResume and the fragment's onResume changes nothing.
My adapter is populated in Activity A's onCreate.
I also tried inserting the adapter.notifyDataSetChanged in a runOnUiThread.
Any one tackled this situation before?
It is not a good suggestion but try to reinitialize the adapter and set it again and tell me if it is work fine.
You should populate your adapter in the fragment in either the onAttach(), or onViewCreated() methods. You could also populate it in the onCreate() of the fragment, then call notifyDataSetChanged in either of these lifecycle methods I suggest.
These methods will be called whenever the fragment is connected and drawn, thus, you can be sure that it will populate your Fragment correctly.
In general, have all your view related items (like adapter creation and population) in your fragment, and navigation (ActionBar, and flowing between Activities) in your Activity.

Hiding the "Loading..." indicator when implementing a ListFragment

I have created display using Fragments, both of which is being populated with data pulled from the internet. While the code itself works as expected without any issues, one of a Fragments (implemented as ListFragment) displays an indeterminant progress indicator within the fragment, which is skewed off to the side. I wish to remove the indicator and use a different ProgressDialog I implemented as the indicator instead.
Doing some research, I have discovered the function setListShownNoAnimation() (documented here) in the ListFragment class, but the following attempts didn't work:
Calling setListShownNoAnimation() in the fragment's onActivityCreated()
Calling it in the parent activity's onCreate
Calling it in the fragment's onCreateView() (this caused an IllegalStateException)
How can I remove the fragment's progress indicator?
The right way to do this is to call:
setListShown(true);
when you need the ProgressBar to be hidden and show the ListView. Call this when you are finished getting the data in the background and are ready to show the list.
This workaround may not be the best method to solve this, but it works nicely:
What I did was first make the Fragment not display at all when I declared it in the layout.xml file:
<fragment class="com.example.MyListFragment"
android:id="#+id/frag_list"
android:layout_width="0dp"
android:layout_height="match_parent"
android:visibility="gone" />
Then after the data has been downloaded and processed, I would then display the Fragment using a FragmentTransaction:
FragmentManager fm = getFragmentManager();
Fragment frag = fm.findFragmentById(R.id.frag_list);
FragmentTransaction ft = fm.beginTransaction();
ft.show(frag);
ft.commit();
If there is a better way to resolve this issue, I'm open to suggestions.
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
adapter.swapCursor(cursor);
if(isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
But you have to setListShown(false) where you set the ListAdapter. This works fine.

Categories

Resources