RecyclerView didn't show up - android

I have faced with a problem that my RecyclerView didn't show up after it gets the data from server side.
When I am trying to debug my code - sometimes it went to DataAdapter class - sometimes not. Unfortunatly, I don't understand why it happends.
I am using the MVP pattern, and maybe I am setting data to adapter wrong.
I'll try to put code here and explain what am I doing.
First of all my card view xml file:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/card_view"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_margin="5dp"
card_view:cardCornerRadius="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ImageView android:id="#+id/info_image"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_weight="1.0"
android:scaleType="centerCrop"/>
<TextView
android:id="#+id/info_text"
android:layout_marginLeft="5dp"
android:layout_marginBottom="5dp"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
</LinearLayout>
</android.support.v7.widget.CardView>
Here is my Adapter for Recycler view:
public class DataAdapter extends RecyclerView.Adapter<DataAdapter.ViewHolder> {
private AutoService[] autoServices = new AutoService[]{};
private Context context;
private static final int IMAGE_WIDTH = 120;
private static final int IMAGE_HEIGHT = 60;
//TODO check how to properly set data on view from presenter
public void setAutoServices(AutoService[] autoServices) {
this.autoServices = autoServices;
}
public DataAdapter(Context context) {
this.context = context;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
CardView cardView = (CardView) LayoutInflater.from(parent.getContext()).
inflate(R.layout.auto_service_card_view, parent, false);
return new ViewHolder(cardView);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
CardView cardView = holder.cardView;
ImageView imageView = (ImageView)cardView.findViewById(R.id.info_image);
Picasso.with(context).load(autoServices[position].getImageURL()).resize(IMAGE_WIDTH, IMAGE_HEIGHT).into(imageView);
TextView textView = (TextView)cardView.findViewById(R.id.info_text);
textView.setText(autoServices[position].getServiceName());
}
#Override
public int getItemCount() {
return autoServices.length;
}
public class ViewHolder extends RecyclerView.ViewHolder{
private CardView cardView;
public ViewHolder(CardView card) {
super(card);
cardView = card;
}
}
}
In my fragment(view) I have a setter which sets the data for adapter.
Date went from presenter which calls data from server side useng rest template.
Here is my presenter:
public class RecyclerViewPresenter implements RecyclerViewContract.Presenter {
private RestTemplate restTemplate;
private RecyclerViewContract.View view;
public RecyclerViewPresenter(RecyclerViewContract.View view){
this.view = view;
new RecyclerViewTask().execute();
}
private class RecyclerViewTask extends AsyncTask<Void, Void, AutoService[]>{
#Override
protected AutoService[] doInBackground(Void... voids) {
restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter());
ResponseEntity<AutoService[]> autoServiceEntity = restTemplate.getForEntity(URL.getAllAutoServices(), AutoService[].class);
return autoServiceEntity.getBody();
}
#Override
protected void onPostExecute(AutoService[] autoServices) {
view.setAutoServiceListOnAdapter(autoServices);
}
}
}
For some reason when my activity with fragment(RecyclerView) starts - the recyclerView someTimes shows up sometimes not. May be am I setting data for adapter wrong using MVP?
Also I am using the Picasso library - and pictures didn't load from url path, for example URL: https://pbs.twimg.com/profile_images/616076655547682816/6gMRtQyY.jpg .
May be the bproblem in Picasso library ?
Any suggestions please ?
public class RecyclerViewFragment extends Fragment implements RecyclerViewContract.View{
private RecyclerViewContract.Presenter presenter;
private DataAdapter dataAdapter;
public RecyclerViewFragment() {
// Required empty public constructor
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(presenter == null){
presenter = new RecyclerViewPresenter(this);
}
}
public static RecyclerViewFragment getInstance(){
return new RecyclerViewFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
RecyclerView recyclerView = (RecyclerView) inflater.inflate(R.layout.fragment_recycler_view, container, false);
dataAdapter = new DataAdapter(getActivity());
recyclerView.setAdapter(dataAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
return recyclerView;
}
#Override
public void setPresenter(RecyclerViewContract.Presenter presenter) {
this.presenter = presenter;
}
#Override
public void setAutoServiceListOnAdapter(AutoService[] autoServices) {
dataAdapter.setAutoServices(autoServices);
}
Also when I am trying to open activity with recycler view and it didn't shows I have next log:
W/EGL_emulation: eglSurfaceAttrib not implemented
W/OpenGLRenderer: Failed to set EGL_SWAP_BEHAVIOR on surface 0xe1b01280, error=EGL_SUCCESS
D/OpenGLRenderer: endAllStagingAnimators on 0xeb8d2e80 (RippleDrawable) with handle 0xe1b2dab0
When I am reopen the activity with recycler view and it shows - I have next log:
W/Settings: Setting airplane_mode_on has moved from android.provider.Settings.System to android.provider.Settings.Global, returning read-only value.
D/OpenGLRenderer: endAllStagingAnimators on 0xeb8d2e80 (RippleDrawable) with handle 0xe25165b0
A/libc: Fatal signal 4 (SIGILL), code 2, fault addr 0xf6f1d46e in tid 2539 (16/6gMRtQyY.jpg)
and application crashes after this.
Check my answer.

Try to return proper fragment root viewGroup, not the widget view.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recycler_view, container, false);
RecyclerView recyclerView = (RecyclerView)view.findViewById(R.id.recyclerView);
dataAdapter = new DataAdapter(getActivity());
recyclerView.setAdapter(dataAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
return view;
}

Edit: I have added dataAdapter.notifyDataSetChanged(); line in place where adapter gets data.
#Override
public void setAutoServiceListOnAdapter(AutoService[] autoServices) {
dataAdapter.setAutoServices(autoServices);
dataAdapter.notifyDataSetChanged();
}

Related

Android: Recyclerview in Bottom Navigation Bar Fragment populated with SQLite will not update

I have a BottomNavigationBar with 3 fragments. In the first fragment, I try to put SQLite data into a recyclerview. It works fine except for the fact that I need to switch between the Navigation Bar items in order to see the refreshed recyclerview. When I use a handler with postDelayed however, it does show the refreshed recyclerview if I set around 1 sec of delay. 0.2 secs wont work already.
Even though this is still very generic: is there any best practice for this? It seems to me that I need to use AsyncTask which has been -however- deprecated.
Thanks!
Simon
HomeFragment
public class HomeFragment extends Fragment {
private HomeViewModel homeViewModel;
private Context context;
private CardView cardview;
private LinearLayout.LayoutParams layoutparams;
private TextView textview;
private RelativeLayout relativeLayout;
private myDbAdapter helper;
RecyclerView myView;
public View onCreateView(#NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
homeViewModel =
new ViewModelProvider(this).get(HomeViewModel.class);
View root = inflater.inflate(R.layout.fragment_home, container, false);
helper = new myDbAdapter(getContext());
myView = (RecyclerView) root.findViewById(R.id.recyclerview_home);
RecyclerViewAdapter3 adapter = new RecyclerViewAdapter3(new ArrayList<String>(Arrays.asList(helper.classes())));
myView.setHasFixedSize(true);
myView.setAdapter(adapter);
LinearLayoutManager llm = new LinearLayoutManager(getContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
myView.setLayoutManager(llm);
homeViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
textView.setText(s);
}
});
return root;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
}
public void refresh(View v){
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
myView = (RecyclerView) v.findViewById(R.id.recyclerview_home);
helper = new myDbAdapter(v.getContext());
ArrayList<String> classes = new ArrayList(Arrays.asList(helper.classes()));
ArrayList<String> subClasses = new ArrayList(Arrays.asList(helper.subClasses()));
RecyclerViewAdapter3 adapter = new RecyclerViewAdapter3(classes);
myView.setHasFixedSize(true);
myView.setAdapter(adapter);
LinearLayoutManager llm = new LinearLayoutManager(v.getContext());
llm.setOrientation(LinearLayoutManager.VERTICAL);
myView.setLayoutManager(llm);
}
}, 1000); //time in millis
}
}
RecyclerViewAdapter3
public class RecyclerViewAdapter3 extends RecyclerView.Adapter<RecyclerViewAdapter3.MyViewHolder> {
public ArrayList<String> classArrayList;
public ArrayList<String> subClassArrayList;
myDbAdapter helper;
public RecyclerViewAdapter3(ArrayList<String> classArrayList){
this.classArrayList = classArrayList;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View listItem = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview, parent, false);
return new MyViewHolder(listItem);
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.class.setText(classArrayList.get(position));
holder.delete.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
helper = new myDbAdapter(v.getContext());
helper.delete(classArrayList.get(position));
HomeFragment homeFragment = new HomeFragment();
homeFragment.refresh(v.getRootView());
}
});
holder.selectButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
}});}
#Override
public int getItemCount() {
return classArrayList.size();
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
private TextView class;
private Button selectButton;
private ImageView delete;
public MyViewHolder(View itemView) {
super(itemView);
class = (TextView)itemView.findViewById(R.id.name);
selectButton = (Button) itemView.findViewById(R.id.selectButton);
delete = (ImageView) itemView.findViewById(R.id.delete);
}
}
}
Thanks for posting your code :)
There are a fair few things that can go wrong in your code as it is right now, and I can't really pinpoint what causes it to work when you use postDelay. I'm going to list a few, which you can look into:
From your onClick() inside your ViewHolder
HomeFragment homeFragment = new HomeFragment();
homeFragment.refresh(v.getRootView());
You should really not instantiate your fragments like this. You can instead pass a callback from your fragment to your adapter (eg.: View.OnClickListener)
You keep re-instantiating your adapter and your helper needlessly. You should create your adapter only once, set it as your recycler view adapter, and save it in a member variable.
Proposed solution
I see that you're already using ViewModel, so you're on a great path for a less error-prone screen, so I suggest that you move your db query-ing logic to your view model. If you're using raw SQLite (instead of Room), you can extend AndroidViewModel, so you'll have access to a context right away. And as you do with your homeViewModel.getText(), you should expose the classes array as live data, observe it, and submit the new list to your adapter.
For submitting your list to your adapter I suggest using ListAdapter, this will provide you a submitList method for submitting the list in the fragment, and inside the adapter, you will have a getItem(int position) method, which you can query inside the onBindViewHolder method.
Inside your fragment, it'll look something like this:
ClassAdapter adapter = null;
View onCreateView(Bundle savedInstanceState) {
super.onCreate(savedInstanceState)
View root = inflater.inflate(R.layout.fragment_home, container, false);
adapter = new ClassAdapter(
new ClassDeleteCallback() {
#Override
void onClassDeleted(Class class) {
// inside view model we can modify db state, than refresh live data
viewModel.deleteClass(class);
}
},
new ClassSelectedCallback() {
// follows same pattern of above
}
);
RecyclerView rv = root.findViewById(R.id.my_rv);
rv.setAdapter(adapter);
rv.setLayoutManager(new LinearLayoutManager(getContext());
homeViewModel.getClasses().observe(getViewLifecycleOwner(), new Observer<List<Class>>() {
#Override
public void onChanged(#Nullable List<Class> classes) {
adapter.submitList(classes);
}
});
homeViewModel.refreshClasses();
return root;
}
I can highly recommend for you to study this project a bit, because it covers lot of the basics which can lead to a much stabler app: Sunflower sample app
I think you should read a bit more about the architecture components, and then go through some code-labs and stuff, and have another go with this screen starting from square one, because it will be easier than fixing the current state :)
I hope this was helpful, and not too discouraging!

Can't get FirestoreRecyclerAdapter to show items

I'm updating my app to use Firebase's Firestore database. I'm struggling to get the app to show the data that's been retrieved from the database. The data is retrieved ok, but doesn't show up. By setting breakpoints, I've established that the ViewHolder isn't being bound to the adapter at any point.
The data is being shown in a Fragment. The Fragment layout is (I've taken out irrelevant stuff like padding, sizes etc):
<android.support.constraint.ConstraintLayout
android:id="#+id/layout_charts_list"
tools:context="apppath.fragments.ChartListFragment">
<TextView
android:id="#+id/loading_list"
android:text="#string/loading_my_charts" />
<TextView
android:id="#+id/empty_list"
android:text="#string/my_charts_empty"
android:visibility="gone"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/charts_list" />
</android.support.constraint.ConstraintLayout>
The Fragment code itself is:
public abstract class ChartListFragment extends Fragment {
private FirebaseFirestore mDatabaseRef;
private FirestoreRecyclerAdapter<Chart, ChartViewHolder> mAdapter;
private Query mChartsQuery;
private RecyclerView mRecycler;
public ChartListFragment() {}
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
View rootView = inflater.inflate(
R.layout.fragment_charts_list, container, false);
mRecycler = rootView.findViewById(R.id.charts_list);
mRecycler.setHasFixedSize(true);
// Set up Layout Manager, and set Recycler View to use it
LinearLayoutManager mManager = new LinearLayoutManager(getActivity());
mManager.setReverseLayout(true);
mManager.setStackFromEnd(true);
mRecycler.setLayoutManager(mManager);
// Connect to the database, and get the appropriate query (as set in the actual fragment)
mDatabaseRef = FirebaseFirestore.getInstance();
mChartsQuery = getQuery(mDatabaseRef);
// Set up Recycler Adapter
FirestoreRecyclerOptions<Chart> recyclerOptions = new FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.build();
mAdapter = new ChartListAdapter(recyclerOptions);
// Use Recycler Adapter in RecyclerView
mRecycler.setAdapter(mAdapter);
return rootView;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Add listener to charts collection, and deal with any changes by re-showing the list
mChartsQuery.addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot queryDocumentSnapshots,
#Nullable FirebaseFirestoreException e) {
if (queryDocumentSnapshots != null && queryDocumentSnapshots.isEmpty()) {
((MainActivity) getActivity()).setPage(1);
mRecycler.setVisibility(View.GONE);
}else {
mRecycler.setVisibility(View.VISIBLE);
}
}
});
}
// HELPER FUNCTIONS
public abstract Query getQuery(FirebaseFirestore databaseReference);
}
ChartListAdapter is as follows:
public class ChartListAdapter
extends FirestoreRecyclerAdapter<Chart, ChartViewHolder> {
public ChartListAdapter(FirestoreRecyclerOptions recyclerOptions) {
super(recyclerOptions);
}
#Override
protected void onBindViewHolder(ChartViewHolder holder, int position, Chart model) {
holder.setChartName(model.getName());
// Bind Chart to ViewHolder
holder.bindToChart(model);
}
#Override
public ChartViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_chart, parent, false);
return new ChartViewHolder(view);
}
}
ChartViewHolder:
public class ChartViewHolder extends RecyclerView.ViewHolder {
private TextView chartNameView;
private String chartKey;
public ChartViewHolder(View itemView) {
super(itemView);
chartNameView = itemView.findViewById(R.id.chart_name);
}
public void setChartName(String chartName) {
chartNameView.setText(chartName);
}
public void bindToChart(Chart chart) {
chartKey = chart.getKey();
chartNameView.setText(chart.getName());
}
public String getChartKey() {
return chartKey;
}
}
The ChartListAdapter constructor is called, but onBindViewHolder and onCreateViewHolder are never called, and ChartViewHolder is never accessed at all. Am I missing a line of code? Or doing this completely wrong? I'm not all that familiar with Adapters and RecyclerViews, so I've found it quite hard to get to grips with putting it all together.
For those who came here from Google, set the lifecycle owner on the options so that start/stop listening is called automatically.
FirestoreRecyclerOptions<Chart> recyclerOptions = FirestoreRecyclerOptions.Builder<Chart>()
.setQuery(mChartsQuery, Chart.class)
.setLifecycleOwner(this)
.build();
You need to add the following, to begin listening for data, call the startListening() method. You may want to call this in your onStart() method. Make sure you have finished any authentication necessary to read the data before calling startListening() or your query will fail:
#Override
protected void onStart() {
super.onStart();
adapter.startListening();
}
more info here:
https://github.com/firebase/FirebaseUI-Android/tree/master/firestore

onCreateViewHolder is never called

I am trying to use recyclerView which is inside a fragment.This is fragment is nested inside the viewPager.
public class UniversityDetail extends Fragment {
RecyclerView universityDetailView;
//need to set Adapter
public static UniversityDetail newInstance(){
return new UniversityDetail();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_university_lsit,container,false);
universityDetailView = (RecyclerView)view;
setupViews();
return view;
}
private void setupViews(){
//set the adapter
UniversityDetailAdapter detailAdapter = new UniversityDetailAdapter(new ArrayList<UniversityDetails>());
universityDetailView.addItemDecoration(new RecyclerListDecorater(getActivity()));
universityDetailView.setAdapter(detailAdapter);
universityDetailView.setLayoutManager(new LinearLayoutManager(getActivity()));
universityDetailView.setHasFixedSize(false);
}
public RecyclerView getRecyclerView(){
return this.universityDetailView;
}
}
This is the fragment which i want to be inside the viewPager.It returns a recyclerView from onCreateView.
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/scroll"
android:paddingBottom="8dp"
android:paddingTop="?attr/actionBarSize"
android:scrollbars="vertical">
fragment_university_lsit.xml
recyclerView uses below adapter.
public class UniversityDetailAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<UniversityDetails> universityDetails;
private static Map<String,String> admissionRecommendation;
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((DetailHolder)holder).bind(universityDetails.get(position));
}
#Override
public int getItemCount() {
return universityDetails.size();
}
public UniversityDetailAdapter(List<UniversityDetails> details){
this.universityDetails = details;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,int viewType){
Log.d("CreateUniveristyDetail", "onCreateViewHolder: detail called");
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.detail_fragment,parent,false);
return new DetailHolder(itemView);
}
}
DetailHolder is the class extends ReyclerView.ViewHolder and its implementation is irrelevant here.
The adapter list is upated from handler and notifyDataSetChanged()
private void setup(){
mHandler = new Handler(Looper.getMainLooper()){
#Override
public void handleMessage(Message message){
if(message.what == ConnectionHandler.DETAILMSG){
Log.d("detail handle message", "handleMessage: Called");
List<UniversityDetails> details = (List<UniversityDetails>)message.obj;
UniversityDetailAdapter adapter = (UniversityDetailAdapter)universityDetail.getRecyclerView().getAdapter();
adapter.addAll(details);
adapter.notifyDataSetChanged();
Log.d("size", "handleMessage: " + adapter.getItemCount());
}
}
};
}
Fragment is created in activity..
private void setupNavigation(){
ViewPagerAdapter pagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
universityDetail = UniversityDetail.newInstance();
pagerAdapter.addFragment(universityDetail);
mViewPager.setAdapter(pagerAdapter);
//setup the fragment transaction.
headerTab.setViewPager(mViewPager,0);
//no need to add to back stack
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.add(R.id.view_pager,universityDetail);
transaction.commit();
}
Implementation of DetailHolder
public class DetailHolder extends RecyclerView.ViewHolder{
CardView admissionGeneral;
public View root;
public DetailHolder (View itemView){
super(itemView);
root = itemView;
admissionGeneral = (CardView)root.findViewById(R.id.admission_general);
}
public void bind(UniversityDetails detail){
Log.d("detail bind", "bind: binding to recyclerView");
}
}
here the log of adapter.getItemCount(); return 3; thats means something is being added and onCreateViewHolder is supposed to be called but it never is.
The weird thing is i have implemented another recyclerAdapter is the sameProject and its working perfectly fine.
This problem would be easier to answer if you could show how the universityDetail was assigned from whatever class contains the Handler usage.
It's also not clear how message.obj is able to be cast to a List.
I believe you are using a different instance of the Fragment there than the one that you want to update.
So, you know this line is good
List<UniversityDetails> details = (List<UniversityDetails>)message.obj;
because you see 3 in the output when you do this
Log.d("size", "handleMessage: " + adapter.getItemCount());
Now, these lines could likely be the problem.
UniversityDetailAdapter adapter = (UniversityDetailAdapter)universityDetail.getRecyclerView().getAdapter();
adapter.addAll(details);
adapter.notifyDataSetChanged();
Firstly, there is no addAll method to RecyclerView.Adapter. So I'm not sure how that compiled. Anyways...
Try to keep cross-class references to a minimum. Here, we expose only the method to add the details rather than the entire RecyclerView.
Then, for RecyclerViews, you should notify only the data that was inserted rather than all of it using notifyItemRangeInserted
public class UniversityDetailFragment extends Fragment {
private RecyclerView universityDetailView;
private UniversityDetailAdapter detailAdapter;
private List<UniversityDetails> details = new ArrayList<UniversityDetails>();
...
public void setupViews() {
detailAdapter = new UniversityDetailAdapter(details);
// etc...
}
public void addDetails(List<UniversityDetails> details) {
int curSize = detailAdapter.getItemCount();
this.details.addAll(details);
detailAdapter.notifyItemRangeInserted(curSize, details.size());
}
}
And then just use the instance of your Fragment
if(message.what == ConnectionHandler.DETAILMSG){
Log.d("detail handle message", "handleMessage: Called");
List<UniversityDetails> details = (List<UniversityDetails>) message.obj;
universityDetail.addDetails(details);
Another recommendation would be to use the correct generic type for the holder class to avoid unnecessary casts.
public class UniversityDetailAdapter extends RecyclerView.Adapter<DetailHolder>

Can't save fragment state in Android while configuration is changed

This type of question is asked many times in SO, I have tried all of them, but couldn't achieve what I actually want. In my app I have Fragment, inside that fragment I have a recycler view. recyclerview would be manipulated by the data which I have got from API.
What I want that when rotation is changed, the app doesn't call the API again. To achieve this I know I have to put the data in onSaveInstanceState, If I want to save a complex object then the class must be implements Parcelable. I have done everything that is recommended, but couldn't achieve what I want.
I am sharing my code here, I have created a simplified example.
Code for SimpleFragment.java
public class SimpleFragment extends Fragment {
RecyclerView recyclerView;
ArrayList<TestModel> testModelList = new ArrayList<TestModel>() ;
SimpleAdapter simpleAdapter;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.simple_fragment, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.recycler);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(mLayoutManager);
simpleAdapter = new SimpleAdapter(getActivity());
recyclerView.setAdapter(simpleAdapter);
if(savedInstanceState != null){
//if this fragment starts after a rotation or configuration change, load the existing model from a parcelable
testModelList = savedInstanceState.getParcelableArrayList("key");
// I have done necessary debug , after rotation , it comes here ,
// but suddenly savedInstanceState becomes null and getTestModelList() get called
}else {
//if this fragment starts for the first time, load the list of model
getTestModelList();
}
simpleAdapter.setModel(testModelList);
return rootView;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList("key",testModelList);
}
public void getTestModelList(){
for (int i=0;i<10;i++){
testModelList.add(new TestModel(i,"test"+i));
}
Toast.makeText(getActivity(),"Called",Toast.LENGTH_SHORT).show();
}
}
layout file for SimpleFragment: simple_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler"
android:background="#android:color/black"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
Here is code for my data model TestModel.java
public class TestModel implements Parcelable {
String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public TestModel(int number, String text) {
this.text = text;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.text);
}
protected TestModel(Parcel in) {
this.text = in.readString();
}
public static final Parcelable.Creator<TestModel> CREATOR
= new Parcelable.Creator<TestModel>() {
public TestModel createFromParcel(Parcel in) {
return new TestModel(in);
}
public TestModel[] newArray(int size) {
return new TestModel[size];
}
};
}
My adapter class: SimpleAdapter.java
public class SimpleAdapter extends RecyclerView.Adapter<SimpleAdapter.ViewHolder> {
Context context;
ArrayList<TestModel> testModels;
public SimpleAdapter(Context context) {
this.context = context;
}
public void setModel(ArrayList<TestModel> model){
this.testModels = model;
notifyDataSetChanged();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.single_row, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.list_item.setText(testModels.get(position).getText());
}
#Override
public int getItemCount() {
return testModels.size();
}
public class ViewHolder extends RecyclerView.ViewHolder{
TextView list_item;
public ViewHolder(View itemView) {
super(itemView);
list_item = (TextView) itemView.findViewById(R.id.list_item);
}
}
}
Code for single row layout for adapter single_row.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:id="#+id/list_item"
android:textColor="#android:color/white"/>
</LinearLayout>
These are what I have done, and if change the orientation getTestModelList() method in my SimpleFragment is called again, which I want to avoid; in real app scenario this method will do some API call. I want to save the state of ArrayList<TestModel> testModelList.
you need to save state of activity and check it before configuration
follow this code
public class MainActivity extends AppCompatActivity{
private HomeFragment homeFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
if (savedInstanceState == null) {
homeFragment = new HomeFragment();
((fm.beginTransaction()).replace(R.id.mainframe, homeFragment)).commit();
}
}}}
You can save Fragment instance like this in Fragment onCreate method for orientation change to save instance :
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set Instance true
this.setRetainInstance(true);
}
Android recreates your activity whenever the orientation changes. The reason behind this is you may need to change your layout but you can still achieve that with onConfigurationChanged() of the Activity or the Fragment I suggest you to add
android:configChanges="orientation|keyboardHidden|screenSize"
in your manifest file (refer to this and this). This will stop recreation of Activity on Confifuration changes.

Can't make Fragment with RecyclerView work

I have to create a list inside one of my Fragments but I cannot make recyclerview work, I was searching the internet whole day followed tutorials but could not figure out why its not working
row_item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/list"
android:name="com.streak.roadpoliceviolations.ViolationFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context="com.streak.roadpoliceviolations.ViolationFragment"
tools:listitem="#layout/fragment_violation" />
Adapter:
public class ViolationAdapter extends RecyclerView.Adapter<ViolationAdapter.MyViewHolder>{
private LayoutInflater inflater;
List<ViolationItem> data= Collections.emptyList();
public ViolationAdapter(List<ViolationItem> data) {
//inflater = LayoutInflater.from(context);
this.data = data;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.fragment_violation, parent, false);
MyViewHolder holder = new MyViewHolder(view);
return holder;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
ViolationItem current = data.get(position);
holder.txt1.setText(current.txt1);
holder.txt2.setText(current.txt2);
}
#Override
public int getItemCount() {
return 0;
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView txt1;
TextView txt2;
public MyViewHolder(View itemView) {
super(itemView);
txt1= (TextView) itemView.findViewById(R.id.txt1);
txt2= (TextView) itemView.findViewById(R.id.txt2);
}
}
}
Fragment class
public class ViolationFragment extends Fragment {
private ViolationAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_violation_list, container, false);
RecyclerView rv = (RecyclerView) view.findViewById(R.id.list);
adapter = new ViolationAdapter(getActivity(),getData());
rv.setAdapter(adapter);
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
return view;
}
public static List<ViolationItem> getData() {
List<ViolationItem> data = new ArrayList<>();
String[] txts1 = {"AAA","BBB","CCC"};
String[] txts2 = {"111","222","333"};
for(int i=0; i<=txts1.length; i++) {
ViolationItem c = new ViolationItem();
c.txt1=txts1[i];
c.txt2=txts2[i];
data.add(c);
}
return data;
}
private List<ViolationItem> createList() {
List<ViolationItem> data = new ArrayList<>();
return data;
}
}
Everything passes without a single warning BUT I cannot place this into the fragment container
In mine activity I'm calling the fragment with the following code
//Initial fragment
ViolationFragment frag = new ViolationFragment();
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction() .replace(R.id.frame_container, frag).commit();
Getting this error during the build
Error:(77, 75) error: incompatible types: ViolationFragment cannot be converted to Fragment
And if i change frag initialization Fragment frag = new ViolationFragment();
I get the same error.
How to finally fix this and make the fragment work?!
As I remember you must use support library fragment to make it work. Please check and confirm.

Categories

Resources