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

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.

Related

How to save and restore scrolling position of the RecyclerView in a fragment when coming back from other activity to the same fragment?

Request to moderators: This is not a duplicate question, please read the below information.
Before asking this question, i'd tried almost every available solution on SO, but none of them worked for me, some resulted in crash and some didn't work at all (I'm a beginner, it is possible that i've done something wrong in my code, i'm not blaming anyone's answer available on SO for not working in my case). Below are my codes, please have a look:
fragment_tab1.xml (which contains recyclerview):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="match_parent"
android:layout_width="match_parent"
android:background="#333333"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="96dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="42dp"
android:background="#color/colorPrimaryDark"
android:id="#+id/sort1"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/sort"
android:layout_marginLeft="10dp"
android:fontFamily="#font/quicksand"
android:textStyle="bold"
android:text="Sort by:"
android:textColor="#ffffff"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
style="#style/Widget.AppCompat.Button.Colored"
android:text="Oldest first"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#style/Widget.AppCompat.Button.Colored"
android:text="Newest first"/>
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_below="#id/sort1"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:padding="5dp" />
</RelativeLayout>
</LinearLayout>
Fragmenttab1.java
public class tab1 extends Fragment {
RecyclerView mRecyclerView;
FirebaseDatabase mFirebaseDatabase;
DatabaseReference mRef;
LinearLayoutManager manager;
private static final String TAG = "tab1";
ProgressDialog progressDialog;
View rootView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//Returning the layout file after inflating
//Change R.layout.tab1 in you classes
rootView = inflater.inflate(R.layout.fragment_tab1, container, false);
return rootView;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recycler_view);
manager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(manager);
mRecyclerView.setItemViewCacheSize(20);
mRecyclerView.setDrawingCacheEnabled(true);
mFirebaseDatabase = FirebaseDatabase.getInstance();
mRef = mFirebaseDatabase.getReference("memes");
mFirebaseDatabase.getInstance().getReference().keepSynced(true);
progressDialog = new ProgressDialog(getActivity());
progressDialog.setMessage("Please Wait");
progressDialog.show();
}
public void onStart() {
super.onStart();
FirebaseRecyclerAdapter<Model, ViewHolder> firebaseRecyclerAdapter =
new FirebaseRecyclerAdapter<Model, ViewHolder>(
Model.class,
R.layout.row2,
ViewHolder.class,
mRef
) {
#Override
protected void populateViewHolder(ViewHolder viewHolder, Model model, int position) {
viewHolder.setDetails(getActivity(), model.getTitle(), model.getDesc(), model.getImage());
progressDialog.cancel();
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewHolder viewHolder = super.onCreateViewHolder(parent, viewType);
viewHolder.setOnClickListener(new ViewHolder.ClickListener(){
#Override
public void onItemClick(View view, int position){
//get data from firebase here
String mTitle = getItem(position).getTitle();
String mDesc= getItem(position).getDesc();
String mImage = getItem(position).getImage();
//pass this data to new activity
Intent intent = new Intent(view.getContext(), ArticleDetail.class);
intent.putExtra("title", mTitle);// put title
intent.putExtra("desc", mDesc);// put description
intent.putExtra("image", mImage); //put image urls
startActivity(intent);
}
#Override
public void onItemLongClick(View view, int position){
//TODO your own implementation on long item click
}
});
return viewHolder;
}
};
mRecyclerView.setAdapter(firebaseRecyclerAdapter);
}
#Override
public void onPause() {
super.onPause();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
View firstChild = mRecyclerView.getChildAt(0);
int firstVisiblePosition = mRecyclerView.getChildAdapterPosition(firstChild);
int offset = firstChild.getTop();
Log.d(TAG, "Postition: " + firstVisiblePosition);
Log.d(TAG, "Offset: " + offset);
preferences.edit()
.putInt("position", firstVisiblePosition)
.putInt("offset", offset)
.apply();
}
#Override
public void onResume(){
super.onResume();
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
}
}
After paying so many days, after trying many many solutions, finally found a working solution for my problem, thanks to https://stackoverflow.com/a/42563804/2128364
I've moved the working solution to another methods to make it work in the fragment.
Maybe this can save someone's day, Here is how it worked:
onConfigurationChanged (as suggested in original solution), onSaveInstanceState or onViewRestoredState methods doesn't work, So you need onPause and onResume methods to save and restore the state respectively.
You have to save the state in onPause method:
mBundleRecyclerViewState = new Bundle();
mListState = mRecyclerView.getLayoutManager().onSaveInstanceState();
mBundleRecyclerViewState.putParcelable(KEY_RECYCLER_STATE, mListState);
And then Restore it in onResume Method:
if (mBundleRecyclerViewState != null) {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
mListState = mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE);
mRecyclerView.getLayoutManager().onRestoreInstanceState(mListState);
}
}, 50);
}
mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
If you don't have already, add the recyclerView dependency in your build.gradle
implementation "androidx.recyclerview:recyclerview:1.2.0"
Than just set the stateRestorationPolicy just after you initialize your list adapter.
listAdapter = ListAdapter()
// Set scroll restore policy
listdapter.stateRestorationPolicy =
RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
For more information on stateRestorationPolicy and different types of policies visit this article
You should use this method that returns the first item visible:
int findFirstVisibleItemPosition();
then you must save this index at onPause() and when user returns retrieve and set it at onResume():
void scrollToPosition (int position)
Problem is scope the variables in this fragment are destroyed when you move to another activity or fragment
Okay finally I think I got the answer for future coders keep all the code in On Create View in fragment it will work maximum times if not try by declaring all variables in class not in local method how to get context in on create view is your home work
If someone minds the accepted answer of it using a static object (static Bundle mBundleRecyclerViewState) to keep state, you can use ViewModel instead.
public class MainViewModel extends ViewModel {
private Parcelable mRecyclerViewState;
/**
* Back up RecyclerView's state to ViewModel in Fragment's onDestroyView
* or in Activity's onStop (or in somewhere else appropriate)
*
* #param layoutManager RecyclerView.LayoutManager object
*/
public void saveRecyclerViewState(RecyclerView.LayoutManager layoutManager) {
mRecyclerViewState = layoutManager.onSaveInstanceState();
}
/**
* Restore backed up RecyclerView's state from ViewModel in onCreate
*
* #param layoutManager RecyclerView.LayoutManager object
*/
public void restoreRecyclerViewState(RecyclerView.LayoutManager layoutManager) {
layoutManager.onRestoreInstanceState(mRecyclerViewState);
}
}
usage example (in Fragment):
private MainViewModel mViewModel;
private GridLayoutManager mLayoutManager;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
mLayoutManager = new GridLayoutManager(requireContext(), ITEMS_PER_ROW);
mViewModel.restoreRecyclerViewState(mLayoutManager);
}
#Override
public void onDestroyView() {
mViewModel.saveRecyclerViewState(mLayoutManager);
super.onDestroyView();
}

Android - RecyclerView multiplies itself every time device is rotated

I have a landscape configuration for one of my app's activities. This activity contains a fragment and this fragment contains one textview and one recyclerview. Everytime when i switch in between portrait and landscape modes, the recyclerview leaves the view of itself like however it was before i turned the device. It might be a little difficult to understand what i try to ask here, so i recorded a gif for that.
https://giphy.com/gifs/3Wv7NAtT8ezP1SQhDu
This is my activity
public class RecipeStepsActivity extends AppCompatActivity {
static Recipe recipe;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_recipe_steps);
if (StepDetailActivity.SDA_TAG.equals(StepDetailActivity.NEGATIVE))
recipe = getIntent().getParcelableExtra("recipe");
Bundle b = new Bundle();
b.putParcelable("recipe", recipe);
ActionBar ab = getSupportActionBar();
if (ab != null)
ab.setTitle(recipe.getName());
RecipeStepsFragment recipeStepsFragment = new RecipeStepsFragment();
recipeStepsFragment.setArguments(b);
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().add(R.id.frame_layout_steps, recipeStepsFragment).commit();
}
}
This is my fragment
public class RecipeStepsFragment extends Fragment {
#BindView(R.id.recipe_steps_rv)
RecyclerView recyclerView;
#BindView(R.id.ingredients_tv)
TextView tv_ingredients;
List<Step> steps;
public RecipeStepsFragment(){}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_recipe_steps, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
List<Ingredients> ingredients;
Recipe recipe = getArguments().getParcelable("recipe");
steps = recipe.getSteps();
initRecyclerView();
ingredients = recipe.getIngredients();
String ingredientsAppended = "INGREDIENTS" + "\n\n";
if (ingredients == null){
ingredientsAppended = "Not Available";
} else {
for (int a = 0 ; a < ingredients.size() ; a++) {
Ingredients i = ingredients.get(a);
ingredientsAppended += String.valueOf(i.getQuantity()) + " " +
i.getMeasure() + " " +
i.getIngredient();
if (a+1 != ingredients.size()){
ingredientsAppended += '\n';
}
}
}
tv_ingredients.setText(ingredientsAppended);
if(savedInstanceState != null){
recyclerView.scrollToPosition(savedInstanceState.getInt("position"));
}
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
outState.putInt("position", recyclerView.getVerticalScrollbarPosition());
super.onSaveInstanceState(outState);
}
private void initRecyclerView(){
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL));
RecipeStepsRecyclerAdapter recipeStepsRecyclerAdapter =
new RecipeStepsRecyclerAdapter(steps, new RecipeStepsRecyclerAdapter.ClickListener() {
#Override
public void onItemClick(int clickedItemPosition) {
Intent intentToStepDetail = new Intent(getActivity(), StepDetailActivity.class);
Step step = steps.get(clickedItemPosition);
intentToStepDetail.putExtra("step", step);
startActivity(intentToStepDetail);
}
}, getContext());
recyclerView.setAdapter(recipeStepsRecyclerAdapter);
recipeStepsRecyclerAdapter.notifyDataSetChanged();
}
}
This is my adapter
public class RecipeStepsRecyclerAdapter extends RecyclerView.Adapter<RecipeStepsRecyclerAdapter.ViewHolder> {
private List<Step> stepList;
private LayoutInflater mInflater;
final private ClickListener clickListener;
public RecipeStepsRecyclerAdapter(List<Step> stepList, ClickListener clickListener, Context context){
this.stepList = stepList;
this.clickListener = clickListener;
mInflater = LayoutInflater.from(context);
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.recipe_steps_recyclerview_adapter, parent, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
Step step = stepList.get(position);
String stepContent = step.getShortDescription();
holder.listingNumber.setText(String.valueOf(position+1));
holder.stepContent.setText(stepContent);
}
#Override
public int getItemCount() {
return stepList.size();
}
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
TextView listingNumber;
TextView stepContent;
private ViewHolder(View itemView) {
super(itemView);
listingNumber = itemView.findViewById(R.id.list_number_tv);
stepContent = itemView.findViewById(R.id.step_content_tv);
itemView.setOnClickListener(this);
}
#Override
public void onClick(View view) {
int clickedPosition = getAdapterPosition();
clickListener.onItemClick(clickedPosition);
}
}
public interface ClickListener{
void onItemClick(int clickedItemPosition);
}
}
Layout files are as I already explained above. I guess nothing special to post here.
Thanks in advance.
The problem is that you add a new fragment to the Activity every time it is created. Here's the end of your onCreate(...):
RecipeStepsFragment recipeStepsFragment = new RecipeStepsFragment();
recipeStepsFragment.setArguments(b);
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().add(R.id.frame_layout_steps, recipeStepsFragment).commit();
The FragmentManager keeps a reference to the fragment you add to it even if the host Activity is destroyed. Thus, you keep adding new instances of the RecipeStepsFragment eventually overlaying each other and producing the seen behavior.
Don't worry, the fix is pretty simple: use replace(...) instead of add(...):
fm.beginTransaction().replace(R.id.frame_layout_steps, recipeStepsFragment).commit();
P.S.: Note, however, that replacing the current fragment with a new one every time the host activity is destroyed is not a good idea. You should check either if the savedInstanceState is null (which indicates that it's a non-recreated Activity) or if the given fragment is already added (define a tag when adding the fragment and try finding it in onCreate(...) before replacing the old one).
Every time you change the rotation of the screen your activity is destroyed and recreated. That is the reason why your list is populating with new items each time when you switch between your orientation.
So it's important you only instantiate your objects once, and not keep recreating them each time your app is recreated. Which then adds them to your list.
You can use onSaveInstanceState and onRestoreInstanceState.
If you want your app to allow orientation change, apply logic so your list don't get new items each time when activity is created.
Put configChanges attribute in your activity in menifest file
<activity android:name=".VideoActivity" android:configChanges="keyboardHidden|orientation|screenSize"/>

RecyclerView didn't show up

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();
}

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>

Gallery type scroll view in android

I am trying to implement a scroller like show in the image below.
I have tried using viewpager but it only shows one item at a time. And I need to show 5 of them and of different sizes. The one in middle needs to be bigger.
Each Item is a frameLayout that contains an ImageView and a TexView, I dont have any problem implementing that part. The problem is it needs to be a scroller and have many items in scroller e.g upto 15 maybe. But should have only 5 items visible at any one time just like shown below. I have tried many implementations. Please some one give me a working example as I have already tried many examples none of them works perfectly. I have waisted more than a week on this one.
You can control it by overriding getPageWidth() in the PagerFragmentAdapter:
#Override
public float getPageWidth(int position) {
return(0.4f);
}
and making sure the size of your images is not too large, so that the page width fits multiple images.
Here are all the steps to set this up:
1) Add a fragment container to your activity layout, where you will load the PhotoPagerFragment:
<!-- PHOTO PAGER FRAGMENT -->
<FrameLayout
android:id="#+id/photoPagerFragmentContainer"
android:layout_width="match_parent"
android:layout_height="150dp"
android:tag="sticky"
android:layout_gravity="center_horizontal" >
</FrameLayout>
2) Inject the PhotoPagerFragment in your activity's onCreate():
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
//Insert the fragment
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.photoPagerFragmentContainer);
if (fragment == null) {
fragment = new PhotoPagerFragment();
fm.beginTransaction()
.add(R.id.photoPagerFragmentContainer, fragment)
.commit();
}
}
3) Create a layout for your PhotoPagerFragment:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black"
android:orientation="vertical" >
<android.support.v4.view.ViewPager
android:id="#+id/photoPager"
android:layout_width="fill_parent"
android:layout_height="120dp"
android:layout_marginTop="2dp"/>
</LinearLayout>
4) Create your PhotoPagerFragment:
public class PhotoPagerFragment extends Fragment {
private ViewPager mPhotoPager;
private PagerAdapter mPhotoAdapter;
public static final String TAG = "PhotoPagerFragment";
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_photo_pager, container, false);
mPhotoAdapter = new PhotoPagerFragmentAdapter(getActivity().getSupportFragmentManager());
mPhotoPager = (ViewPager) view.findViewById(R.id.photoPager);
mPhotoPager.setAdapter(mPhotoAdapter);
return view;
}
}
5) And the adapter:
public class PhotoPagerFragmentAdapter extends FragmentPagerAdapter {
private int[] Images = new int[] {
R.drawable.photo_1, R.drawable.photo_2,
R.drawable.photo_3, R.drawable.photo_4,
R.drawable.photo_5, R.drawable.photo_6
};
private int mCount = Images.length;
public PhotoPagerFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return PhotoDetailsFragment.newInstance(Images[position]);
}
#Override
public int getCount() {
return mCount;
}
#Override
public float getPageWidth(int position) {
return(0.4f);
}
public void setCount(int count) {
if (count > 0 && count <= 10) {
mCount = count;
notifyDataSetChanged();
}
}
}
6) And finally, your PhotoDetailsFragment that will show each image:
public final class PhotoDetailsFragment extends Fragment {
private int photoResId;
private static final String TAG = "PhotoDetailsFragment";
public static final String EXTRA_PHOTO_ID = "com.sample.photo_res_id";
public static PhotoDetailsFragment newInstance(int photoResId) {
Bundle args = new Bundle();
args.putSerializable(EXTRA_PHOTO_ID, photoResId);
PhotoDetailsFragment fragment = new PhotoDetailsFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
photoResId = (Integer)getArguments().getSerializable(EXTRA_PHOTO_ID);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final ImageView image = new ImageView(getActivity());
image.setImageResource(photoResId);
// Hook up the clicks on the thumbnail views
image.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
...
}
});
LinearLayout layout = new LinearLayout(getActivity());
layout.setLayoutParams(new LayoutParams(-1, -1));
layout.setGravity(Gravity.CENTER);
layout.addView(image);
return layout;
}
}

Categories

Resources