Android ViewModel slow navigation on already loaded data - android

Im working on loading data from Firebase Realtime Database by ViewModel. In my layout I have progress bar and recyclerview. Things work perfecly when i need to load my data first, when I click on button fragment opens instantly, progress bar is running and when data loads it stop running and recyclerview shows up. But, when i go into that fragment again (Data is already loaded), no progress bar is shown (which is okay), but it takes about second to comming that switch, which is significantly slower than first behaviour that i described.
So, I am wondering what is making it to wait that long in second scenario and how can I override it, so my fragment shows up first and then shows up recyclerview?
I have already tried using viewstub and dummy views but nothing seems to work..
My CategoryFragment
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
homeViewModel = new ViewModelProvider(requireActivity()).get(MenuViewModel.class);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(root == null || fragmentState == STATE_STARTUP)
{
binding = FragmentCategoryRecyclerBinding.inflate(inflater, container, false);
root = binding.getRoot();
binding.categoryRecycler.setHasFixedSize(true);
binding.categoryRecycler.setLayoutManager(new LinearLayoutManager(getContext(), RecyclerView.VERTICAL, false));
layoutAnimationController = AnimationUtils.loadLayoutAnimation(getContext(), R.anim.layout_item_fade_scale);
myFoodListAdapter = new MyFoodListAdapter(getContext(), foodModelList, String.valueOf(menuIndex), String.valueOf(categoryIndex));
binding.categoryRecycler.setAdapter(myFoodListAdapter);
}
else
binding = FragmentCategoryRecyclerBinding.bind(root);
return root;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
homeViewModel.getMessageError().observe(getViewLifecycleOwner(), s -> Toast.makeText(getContext(), "" + s, Toast.LENGTH_SHORT).show());
homeViewModel.getMenuList(restaurantId).observe(getViewLifecycleOwner(), menuModels -> {
if(fragmentState == STATE_STARTUP || fragmentState == STATE_SEARCHING)
{
binding.categoryRecycler.setLayoutAnimation(layoutAnimationController);
myFoodListAdapter.setFoodModelList(menuModels.get(menuIndex).getCategories().get(categoryIndex).getItems());
binding.progressBar.hide();
binding.categoryRecycler.setVisibility(View.VISIBLE);
fragmentState = STATE_INITIALIZED;
if(foodModelList.isEmpty())
foodModelList = menuModels.get(menuIndex).getCategories().get(categoryIndex).getItems();
myFoodListAdapter.notifyDataSetChanged();
}
});
}
#Override
public void onDestroyView() {
binding = null;
super.onDestroyView();
}

Sorry for the late reply
Use viewmodel or androidviewmodel in onstart instead on create

Related

Recycler View using Firestore goes to top on data changed

I have used a recyclerView with Firestore Database.
Whenever I press the like button on the feed post, the recycler view tends to go to the first post. The clicking on the like button involves 1 write and 1 update operation on a FeedPost object.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View fragView = inflater.inflate(R.layout.fragment_feed, container, false);
db = FirebaseFirestore.getInstance();
feedRecyclerView = (RecyclerView)fragView.findViewById(R.id.feed_recycler_view);
setUpFeedRecyclerView();
return fragView;
}
private void setUpFeedRecyclerView() {
feedRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
getFeed();
}
private void getFeed() {
db.collection("XYZ")
.orderBy("time_posted", Query.Direction.DESCENDING).limit(100)
.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(QuerySnapshot snapshots, FirebaseFirestoreException e) {
if (e != null) {
return;
}
List<FeedPost> feedList = new ArrayList<>();
for (QueryDocumentSnapshot doc : snapshots) {
feedList.add(doc.toObject(FeedPost.class));
}
feedAdapter = new FeedAdapter(feedList);
feedRecyclerView.setAdapter(feedAdapter);
feedAdapter.notifyDataSetChanged();
}
});
}
I want to know how I can prevent the scrolling to the top of the recycler view, whenever the like button is clicked.
Avoid setting the adapter inside the event callback. Set the adapter when you're setting up the view and just update the data inside (and preferably use the correct notifyItemInserted/Removed/etc methods).
The best way to handle this is to use FirestoreRecyclerAdapter and let it handle all these use cases.
https://github.com/firebase/FirebaseUI-Android/tree/master/firestore#using-the-firestorerecycleradapter

Reusing layout created on Fragment.onCreateView() to avoid inflation when showing multiple times

I am using a DialogFragment to display a 'modal' bottom sheet menu (more info here: https://material.io/develop/android/components/bottom-sheet-dialog-fragment/). Since it contains a kind of context menu for the items contained in a RecyclerView, it may be shown multiple times during runtime.
However, always DialogFragment.show() is called, Fragment.onCreateView() is also called, which leads to layout inflation, which can(?) be considered as a 'heavy' task to be computed in the UI thread, which I want to avoid for performance reasons. So to avoid layout inflation every time the DialogFragment is shown, I created a ViewGroup member object pointing to the View being returned Fragment.onCreateView() in order to be reused, like this:
public class BottomMenu extends BottomSheetDialogFragment {
private ViewGroup mLayout;
private TextView mLabel;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if (mLayout == null) {
mLayout = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.bottom_sheet, container, false);
mLabel = mLayout.findViewById(R.id.bottom_sheet_label);
}
return mLayout;
}
#Override
public void onDismiss(#NonNull DialogInterface dialog) {
super.onDismiss(dialog);
// The view cannot be reused if it's already attached to the previous parent view
((ViewGroup) mLayout.getParent()).removeView(mLayout);
}
public void setLabel(String label) {
mLabel.setText(label)
}
}
But once used for the first time, such view must be detached from the Fragment container view to be reused (see onDismissed() overriden method on posted snippet), which seems like a nasty workaround
So I post this question to check if anyone knows a better approach to reuse the layout for the same Fragment
More details here:
public class ActivityMain extends AppCompatActivity {
private BottomMenu mBottomMenu;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
[...]
mBottomMenu = new BottomMenu();
}
#Override
public boolean onLongClick(View v) {
mBottomSheet.setLabel(label);
// The following calls onCreateView() in Fragment, so try to return
// there the previously inflated layout, if any
mBottomSheet.show(getSupportFragmentManager(), "TAG?");
return true;
}
}
It is already a nice practice as long as you don't surrender to any possible bugs.. However there are one or two things I want to let you know about resuing dialogFragment.
public class BottomMenu extends BottomSheetDialogFragment {
private ViewGroup mLayout;
private TextView mLabel;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if (mLayout == null) {
mLayout = (ViewGroup) LayoutInflater.from(getContext()).inflate(R.layout.bottom_sheet, container, false);
mLabel = mLayout.findViewById(R.id.bottom_sheet_label);
} else if(mLayout.getParent()!=null) { // it's not a lot of code. just a few lines……
((ViewGroup)mLayout.getParent()).removeView(mLayout);
}
return mLayout;
}
}
One thing is about nested fragments. When the dialogFragment hold a viewpager and the viewpager have serveral sub-fragments, you must reset the viewpager's adapter on the reusing-call of onCreateView. The reason is that after closing the dialogFragment, the old fragmentManager returned by getChildFragmentManager() is no longer valid, and it should be updated.
... onCreateView(...)
if (mLayout == null) {
...
} else {
...
viewpager.setAdapter(new MyFragmentAdapter(getChildFragmentManager(), fragments));
}
If this step is omitted, you may observe strange behaviours when reusing the dialogFragment, such as recyclerviews in the sub-fragments stop updating in response to NotifyDatasetChanged, but if you scroll it, it will update.
Another thing is that I tend to use WeakRefernce to hold the dialogFragment to be reused. I even have an array of them.
In java applications, if you don't use similar mechanism, you can see rapid surge in memory usage when the user open and close the same dialog again and again. So at least it's not a bad practice to reuse dialogs when it's necessary.

How to correctly add code to a Fragment with a Recycler view in XML?

I have only the mainActivity and I use 3 fragnments and navigate through them with a bottomnav. All good until now, Im able to run the app on the emulator but when I select this fragment with my RecyclerView I get this error message and the app crashes
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView.setAdapter(android.support.v7.widget.RecyclerView$Adapter)' on a null object reference
I've seen a lot of alike answers and try to make my way arround but no success, so Im thinking Im not putting the code in the correct way or in the correct places, can you give me some advice?
Here is the Fragment code
public class administrador_atletas extends Fragment {
//Lista de atletas
public List<lista_atletas> lista_atl;
public RecyclerView rcc_lista_atletas;
public lista_atletas_adaptador adaptador_lista_atletas;
public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_administrador_atletas, container, false);
rcc_lista_atletas = (RecyclerView)view.findViewById(R.id.recycler_administrador_atletas);
return view;
}
#Override
public void onActivityCreated (Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
LinearLayoutManager linear = new LinearLayoutManager(this.getActivity());
linear.setOrientation(LinearLayoutManager.VERTICAL);
rcc_lista_atletas.setLayoutManager(linear);
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// do your variables initialisations here except Views!!!
data();
iniciar_adaptador_atletas();
}
public void onViewCreated(View view, Bundle savedInstanceState){
super.onViewCreated(view, savedInstanceState);
}
public void data(){
lista_atl = new ArrayList<>();
lista_atl.add(new lista_atletas("Astrid Ruvalcaba Ramos", "Esgrima"));
lista_atl.add(new lista_atletas("Daniel Sanchez Cuevas", "G. Artistica"));
lista_atl.add(new lista_atletas("Alexa Luna Contreras", "TKD"));
lista_atl.add(new lista_atletas("Paul Carillo Mendez", "Natacion"));
lista_atl.add(new lista_atletas("Karen Mendoza Galindo", "Boxeo"));
lista_atl.add(new lista_atletas("Marco Torres Miranda", "Tiro con arco"));
}
public void iniciar_adaptador_atletas(){
adaptador_lista_atletas = new lista_atletas_adaptador(lista_atl);
rcc_lista_atletas.setAdapter(adaptador_lista_atletas);
}
Thanks in advance
EDIT: I just moved
data();
iniciar_adaptador_atletas();
Bellow
rcc_lista_atletas.setLayoutManager(linear);
In onCreateView so I have this
public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_administrador_atletas, container, false);
rcc_lista_atletas = (RecyclerView)view.findViewById(R.id.recycler_administrador_atletas);
LinearLayoutManager linear = new LinearLayoutManager(this.getActivity());
linear.setOrientation(LinearLayoutManager.VERTICAL);
rcc_lista_atletas.setLayoutManager(linear);
data();
iniciar_adaptador_atletas();
return view;
}
And it worked, now Im able to enter the fragment with my data
Many thanks to all, your info was very useful!
As the error message says, you are calling the method setAdapter on a null-valued variable (probably rcc_lista_atletas).
While the error is not exactly in the source code you published (you should update your post with the full code), I suppose one of the methods, data() or iniciar_adaptador_atletas(), is calling 'setAdapter'.
You must remember that onCreate is executed before onCreateView. So, you're probably calling setAdapter before the onCreateView is executed and, doing so, rcc_lista_atletas is still null. Move data() and iniciar_adaptador_atletas() to the line before "return view;" in onCreateView and test it again.
This is the best we can do without checking you full code.
It seems, you try to connect adapter to recyclerView before created recyclerView. So, try to move iniciar_adaptador_atletas(); to the bottom of
rcc_lista_atletas = (RecyclerView)view.findViewById(R.id.recycler_administrador_atletas);

private variable inside fragment are all null, why?

I have of null object inside a fragment. The basic idea is that I have an activity that fetches a database asynchronously. However my recyclerview where I will populate the data lives into a fragment. The pseudo-code is more or less
ACTIVITY:
public void onCreate(Bundle savedInstanceState) {
//kicks off a query to the server
mData = new Gson().fromJson(getIntent().getExtras().getString(Constants.MYDATA), MyData.class);
if (mVenue == null) {
finish();
return;
}
// a bunch of stuff
// create a fragment
mMyFrag = new MyFrag();
}
public void CallBackWhenDone(final List<DataSet> dataset) {
// notify the frag that we are done
mMyFrag.notifyDataSetChanged(messages);
}
FRAGMENT:
private RecyclerView mRV;
private ParentActivity mActivity;
private ActivityAsynchData mAsynchData;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recycler, container, false);
mRV = (RecyclerView) view.findViewById(R.id.list);
mRV.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
if (null != mActivity) {
mAsynchData = mActivity.GetAsynchData();
}
if (null != mAsynchData) {
mRV.setAdapter(new MyRecyclerAdapter(getActivity(), mAsynchData));
}
}
// mRV is null when the activity "CallBackWhenDone" calls the frag
// all private variables are gone! why?
public void notifyDataSetChanged(final List<Message> messages) {
MyRecyclerAdapter adapter = ((MyRecyclerAdapter) mRV.getAdapter());
adapter.setMessageList(messages);
adapter.notifyDataSetChanged();
}
I ended up hacking my recycler (mRV in this case) view to be static, but this looks super hacked.
any way around? In other words how can I fetch my "mRV" after the fragment has been created and the private vars are all gone.
What i could understand is, that you initialised the fragment and try to access the recycler view in that but its throwing you null. I am not surprised to see it being as null. The way you have called the method is not correct.You need to get the hosted and already running fragment, instance try doing this:
if you are using support fragment use getSupportFragmentManager instead of getFragmentManager.
MyFrag fragment = (MyFrag) getFragmentManager().findFragmentById(R.id.fragmentHolder);
fragment.<specific_function_name>();
Here , the R.id.fragmentHolder is the id of the frame layout or any layout that you are using to host your fragment inside the an activity.
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragmentHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>

View getting blurred occasionally on a specific Android device

I have come across a problem where the screen of my Android device(Moto G2) gets blurred on click of a specific button in my app. This event happens occasionally and not everytime I click this button.
This scenario is not happening on few other devices (like LG G3) in which I have tested the app for some time.
I couldnot find anything related or useful in the Logs either.
Just to add, the Log in button which is posing this issue is in a view of a fragment attached.
A screenshot of the scenario :
Adding code in the Activity's onCreate() where it adds the Fragments :
if (savedInstanceState == null) {
facebookLoginBtnFragment = new FacebookLoginBtnFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.container, facebookLoginBtnFragment).commit();
googleLoginBtnFragment = new GoogleLoginBtnFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.container2, googleLoginBtnFragment).commit();
} else {
facebookLoginBtnFragment = (FacebookLoginBtnFragment)getSupportFragmentManager().findFragmentById(R.id.container);
googleLoginBtnFragment = (GoogleLoginBtnFragment)getSupportFragmentManager().findFragmentById(R.id.container2);
}
Below is the code of onCreateView() of the Google + fragment :
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_gplusloginbtns, container, false);
gplusButton = (SignInButton)rootView.findViewById(R.id.GPlus_Sign_in_button); gplusButton.setOnClickListener(this);
setGooglePlusButtonText(gplusButton, "Log in with Google");
rootView.findViewById(R.id.GPlus_sign_out_button).setOnClickListener(this);
if (mActivity.getClass().toString().contains("ShowProfile_act")) {
rootView.findViewById(R.id.GPlus_Sign_in_button).setVisibility(View.GONE);
} mConnectionProgressDialog = new ProgressDialog(getActivity()); mConnectionProgressDialog.setMessage("Logging in...");
return rootView;
}
Below is the code for setGooglePlusButtonText() method :
private void setGooglePlusButtonText(SignInButton signInButton, String buttonText) {
// Find the TextView that is inside of the SignInButton and set its text
for (int i = 0; i < signInButton.getChildCount(); i++) {
View v = signInButton.getChildAt(i);
if (v instanceof TextView) {
TextView tv = (TextView) v;
tv.setText(buttonText);
return;
}
}
}
-->The others methods of the fragment deal with connection maintenance part with the Google+ API.I assume they might not be relevant
Any suggestions, advices on the cause and how to avoid such scenarios would be helpful.

Categories

Resources