I want refresh my data in RecyclerView, when I select some item in spinner. RecyclerView is placed in fragment in tabbed activity, My RecyclerView filled from room with livedata. I create observer for RecyclerView , but I don't understand how I can change my observer or do something else.
May be I need recreate this tab...
Please, tell me what i can do.
When I create new observer I getting new data in RecyclerView, but when I interact with this data, I see that all my old observers are live and react to my actioins.
How I can query another data from database and pass it to observer or create new observer and delete old?
My Fragment
public class AllSitesFragment extends Fragment implements AdapterView.OnItemSelectedListener {
#BindView(R.id.recycler_view)
RecyclerView recyclerView;
#BindView(R.id.tvEmptyMessage)
TextView tvEmptyMessage;
#BindView(R.id.spinner)
Spinner spinner;
private WebSitesViewModel mWebSitesViewModel;
private MyRecViewAdapter adapter;
public AllSitesFragment() {}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_all_sites, container, false);
ButterKnife.bind(this, v);
setHasOptionsMenu(true);
mWebSitesViewModel = ViewModelProviders.of(this).get(WebSitesViewModel.class);
adapter = new MyRecViewAdapter(getContext(), mWebSitesViewModel, AllSitesFragment.this );
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(adapter);
//Observer
mWebSitesViewModel.getAllWebSites().observe(this, new Observer<List<WebSites>>() {
#Override
public void onChanged(#Nullable final List<WebSites> webSites) {
adapter.setWebSites(webSites);
if (adapter.getmWebSites().size() == 0) {
tvEmptyMessage.setText(R.string.not_saved_sites);
tvEmptyMessage.setVisibility(View.VISIBLE);
} else {
tvEmptyMessage.setVisibility(View.INVISIBLE);
}
}
});
spinner.setOnItemSelectedListener(this);
mWebSitesViewModel.getAllWebSites().removeObservers(getActivity());
return v;
}
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0:
// Refresh
break;
case 1:
//Refresh
break;
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
You need to filter the results of your getAllWebSites() function in your viewModel, so you need a setter in that viewModel to set/update your filter.
You can do it by using a MutableLiveData variable in your viewModel, which will be used as parameter to filter your Website collection.
This can be done by using a switchMap transformation.
It would be something like this (non-functional, adapt it to your needs) :
ViewModel
public class SitesViewModel extends AndroidViewModel {
private final YourManager yourManager;
private final LiveData<WebSites> webSites;
private final MutableLiveData<YourItem> yourItemFilter;
public SitesViewModel(Application application) {
super(application);
yourManager = new YourManager();
yourItemFilter = new MutableLiveData<>();
webSites = Transformations.switchMap(yourItemFilter, input -> yourManager.getAllWebSites(input));
}
public void setYourItemFilter(YourItem filter) {
yourItemFilter.setValue(filter);
}
public LiveData<WebSites> getAllWebSites() {
return webSites;
}
}
Then in your activity simply set the filter value on item selected :
Activity
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
switch (position) {
case 0:
YourItem item = (YourItem) parent.getItemAtPosition(position);
mWebSitesViewModel.setYourItemFilter(item);
break;
case 1:
...
}
}
Related
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!
I created three tabs with three fragments using view pager.I want to jump to inspiring fragment after clicking on list item defined inside Categories Fragment(Tab Fragment created with view pager).When i click on the list item error occurs.I want to jump to inspiring fragment from categories fragment(fragment defined inside view pager).
Categories (Tab Fragment created with view pager)
public class Categories extends Fragment {
private RecyclerView recyclerView;
private List<CategoriesDataModel> list;
private String[] categories={"Inspiring","Feelings","Strength","Hard Work","Success"};
public Categories() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_categories, container, false);
recyclerView = (RecyclerView) view.findViewById(R.id.categoriesList_Id);
list = new ArrayList<>();
for (int i = 0; i < categories.length; i++) {
CategoriesDataModel dataModel = new CategoriesDataModel();
dataModel.cat_name = categories[i];
list.add(dataModel);
}
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setHasFixedSize(true);
CategoryRecyclerViewAdapter adapter = new CategoryRecyclerViewAdapter(list,getContext());
adapter.setOnItemClickListener(new CategoryRecyclerViewAdapter.ClickListener() {
#Override
public void onItemClick(int position, View v) {
switch (position){
case 0:
getFragmentManager().beginTransaction().replace(R.id.frameLayout_inspiring,new Inspiring()).addToBackStack(null).commit();
}
}
});
recyclerView.setAdapter(adapter);
return view;
}
}
Inspiring :-
public class Inspiring extends Fragment {
public Inspiring() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_inspiring, container, false);
}
}
Pager Adapter :-
public class Pager extends FragmentStatePagerAdapter {
int tabCount=0;
public Pager(FragmentManager fm,int tabCount) {
super(fm);
this.tabCount=tabCount;
}
//this will return tab selected
#Override
public Fragment getItem(int i) {
switch(i) {
case 0:
return new Recents();
case 1:
return new Top();
case 2:
return new Categories();
default:
return null;
}
}
#Override
public int getCount() {
return tabCount;
}
}
You might find the AndroidViewModel of use in this case.
What you are doing is attempting to maintain state between different parts of your app.
If you have an AndroidViewModel attached to the Activity Lifecycle, you can observe that state in your Activity and make transactions to the FragmentManager to represent your choices.
An example
ViewModel
This ViewModel contains state data for which navigation item you are in (representing a Fragment with an integer in this case) and using an integer to represent an index for your inspiration row.
public class NavigationViewModel extends AndroidViewModel {
private MutableLiveData<Integer> navigationLiveData = new MutableLiveData<>();
private MutableLiveData<Integer> inspirationLiveData = new MutableLiveData<>();
public NavigationViewModel(#NonNull Application application) {
super(application);
}
public LiveData<Integer> getNavigation() {
return navigationLiveData;
}
public void setNavigation(Integer id) {
navigationLiveData.postValue(id);
}
public LiveData<Integer> getInspiration() {
return inspirationLiveData;
}
public void setInspiration(Integer id) {
inspirationLiveData.postValue(id);
}
}
Activity
The Activity will observe the navigation LiveData provided by our implementation of the AndroidViewModel. This will let it know immediately when a navigation change has been made.
public class NavigationActivity extends AppCompatActivity {
private NavigationViewModel navigationViewModel;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
navigationViewModel = ViewModelProviders.of(this).get(NavigationViewModel.class);
navigationViewModel.getNavigation().observe(this, id -> {
switch(id) {
case R.id.recents:
// TODO: Load recent fragment here with a transaction
break;
case R.id.top:
// TODO: Load top fragment here with a transaction
break;
case R.id.categories:
// TODO: Load categories fragment here with a transaction
break;
case R.id.inspiring:
// TODO: Load inspiring fragment here with a transaction
break;
}
});
}
}
Inspiration Fragment
This Fragment will observe the inspiration index provided by our implementation of AndroidViewModel. That lets it know what content needs to be displayed. This can be set from ANYWHERE.
public class InspiringFragment extends Fragment {
private NavigationViewModel navigationViewModel;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_inspiring, container, false);
navigationViewModel = ViewModelProviders.of(this).get(NavigationViewModel.class);
navigationViewModel.getInspiration().observe(getViewLifecycleOwner(), inspiration -> {
// TODO: Update the root view UI with data gleaned using the inspiration index given here
});
return root;
}
}
Setting it
Once you have that, all you need to do is call:
navigationViewModel.setInspiration(1);
navigationViewModel.setNavigation(R.id.inspiration);
This should give you a good base to start with.
Hey guys I really need your help. I've spent like 5 days trying to get my recyclerview to update only when the user presses OK on a dialogbox, that appears from the menu actionbar. I've tried every possible method I could think of, every method I've seen on stackoverflow, YouTube, etc. and none of them worked for me.
How do I get the recyclerview in a fragment to update after dialogbox closes? I know there are similar questions regarding updating the menu, and (recyclerviews with dialogfragments), but none of them have a combination.
Out of the countless attempts, the current code configuration posted below isn't causing any errors, however, the recyclerview remains blank. The closest attempt I had to finding a solution, was creating an adapter and setting up the recycler in onOptionsItemSelected. But obviously, it updates only when the user clicks the button, and the initial click would create a blank recyclerview.
Fragment:
(There's a lot of repeated commented code from different attempts)
public class ExerciseRoutine extends Fragment implements ExerciseRoutine_Dialog.RoutineDialogListener{
private String Routine_name, Routine_split;
private ArrayList<ExerciseRoutine_Information> Routine_information = new ArrayList<>();
private RecyclerView recyclerView;
private RecyclerView.Adapter adapter;
private RecyclerView.LayoutManager layoutManager;
#Override
public void sendInput(String name, String split, RecyclerView.Adapter DialogAdapter) {
Routine_name = name;
Routine_split = split;
adapter = DialogAdapter;
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exercise_routine_fragment, container, false);
//Report that this fragment would like to participate in populating the options menu by
//receiving a call to onCreateOptionsMenu(Menu, MenuInflater) and related methods.
//If true, the fragment has menu items to contribute.
setHasOptionsMenu(true);
recyclerView = view.findViewById(R.id.ExerciseRoutine_Recycler);
//BuildRecyclerView();
//recyclerView.setHasFixedSize(true); //If the Recyclerview is static
/*Routine_information.add(new ExerciseRoutine_Information(Routine_name, Routine_split));
recyclerView = view.findViewById(R.id.ExerciseRoutine_Recycler);
//recyclerView.setHasFixedSize(true); //If the Recyclerview is static
layoutManager = new LinearLayoutManager(getActivity());
adapter = new ExerciseRoutineAdapter(Routine_information);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);*/
return view;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.exercise_routine_menu, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case R.id.action_addRoutine:
ExerciseRoutine_Dialog routineDialog = new ExerciseRoutine_Dialog();
routineDialog.setTargetFragment(ExerciseRoutine.this, 1);
routineDialog.show(getFragmentManager(), "Routine Dialog");
//Routine_information.add(new ExerciseRoutine_Information(Routine_name, Routine_split));
BuildRecyclerView();
//adapter.notifyItemInserted(Routine_information.size());
//if(!Routine_name.equals("") && !Routine_split.equals("")) {
//}
}
return super.onOptionsItemSelected(item);
}
public void BuildRecyclerView(){
layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
}
public void BuildAdapter(){
//adapter = new ExerciseRoutineAdapter(getContext(),Routine_information);
adapter.notifyItemInserted(Routine_information.size());
}
}
My Dialog Fragment:
public class ExerciseRoutine_Dialog extends DialogFragment{
private TextView ActionOK, ActionCANCEL;
private EditText Routine_name, Routine_split;
private RoutineDialogListener activityCommander;
private ArrayList<ExerciseRoutine_Information> Routine_information = new ArrayList<>();
private RecyclerView.Adapter adapter;
public interface RoutineDialogListener{
void sendInput(String name, String split, RecyclerView.Adapter DialogAdapter);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try{
activityCommander = (RoutineDialogListener) getTargetFragment();
}catch(ClassCastException e){
throw new ClassCastException(context.toString() + "Must Implement RoutineDialogListener");
}
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exercise_routine_dialog, container, false);
Routine_name = view.findViewById(R.id.ExerciseRoutine_DialogNameInput);
Routine_split = view.findViewById(R.id.ExerciseRoutine_DialogSplitInput);
ActionOK = view.findViewById(R.id.ExerciseRoutine_DialogAction_OK);
ActionCANCEL = view.findViewById(R.id.ExerciseRoutine_DialogAction_CANCEL);
//recyclerView = view.findViewById(R.id.ExerciseRoutine_Recycler);
ActionCANCEL.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getDialog().dismiss();
}
});
ActionOK.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String name = Routine_name.getText().toString();
String split = Routine_split.getText().toString();
if(!name.equals("") && !split.equals("")) {
Routine_information.add(new ExerciseRoutine_Information(name, split));
adapter = new ExerciseRoutineAdapter(getContext(), Routine_information);
activityCommander.sendInput(name, split, adapter);
adapter.notifyItemInserted(Routine_information.size());
}
getDialog().dismiss();
}
});
return view;
}
}
Your current approach seems to be to pass the RecyclerView.Adapter to the DialogFragment and try to insert the new data on-the-spot. I think this is a problematic setup. The dialog's purpose is to offer some means for the users to enter the required data, period. It should not be tasked with the job of managing the RecyclerView or its Adapter because that way your components will be very tightly coupled:
Imagine that first you use a ListView in your implementation, and suddenly someone decides to ban every ListView from your app (maybe for performance reasons) and has you exchange them all for RecyclerViews. Then your approach would force you to change the code for the DialogFragment (it would have to cater to a different type of Adapter). A more loosely coupled implementation would enable you to make changes to one component without having to rewrite too many others.
That's why I won't try to make your code work as-is, instead I'd like to show you another way:
Because the RecyclerView is part of the Fragment's UI, the Fragment is the place where code related to managing the RecyclerView belongs. It is basically possible to have the Adapter as an inner class of the Fragment but I prefer having it as a standalone class if the code gets a little longer, and also because it enforces "decoupling".
Interfaces play a very important part in good architecture, so the DialogFragment will still make use of an interface to send its data. But it's up to the class which actually implements the interface (here: the Fragment) to pass the data to any interested third parties, e.g. the RecyclerView.Adapter (The Adapter in turn could have its own interface to publish important events like clicks on list items).
Having said that, here are some code snippets:
The DialogFragment
public class ExerciseRoutine_Dialog extends DialogFragment {
private EditText Routine_name, Routine_split;
public interface RoutineDialogListener{
/**
* There is some new ExerciseRoutine_Information
*/
void sendInput(String name, String split);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exercise_routine_dialog, container, false);
Routine_name = view.findViewById(R.id.ExerciseRoutine_DialogNameInput);
Routine_split = view.findViewById(R.id.ExerciseRoutine_DialogSplitInput);
TextView actionOK = view.findViewById(R.id.ExerciseRoutine_DialogAction_OK);
TextView actionCANCEL = view.findViewById(R.id.ExerciseRoutine_DialogAction_CANCEL);
actionCANCEL.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getDialog().dismiss();
}
});
actionOK.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String name = Routine_name.getText().toString();
String split = Routine_split.getText().toString();
if(!name.equals("") && !split.equals("")) {
// just send the input to the main Fragment
RoutineDialogListener listener = getListener();
if(listener != null) {
listener.sendInput(name, split);
}
}
getDialog().dismiss();
}
});
return view;
}
/**
* Tries to find a suitable listener, examining first the hosting Fragment (if any) and then the Activity.
* Will return null if this fails
* #return x
*/
private RoutineDialogListener getListener(){
RoutineDialogListener listener;
try{
Fragment onInputSelected_Fragment = getTargetFragment();
if (onInputSelected_Fragment != null){
listener = (RoutineDialogListener) onInputSelected_Fragment;
}
else {
Activity onInputSelected_Activity = getActivity();
listener = (RoutineDialogListener) onInputSelected_Activity;
}
return listener;
}catch(ClassCastException e){
Log.e("Custom Dialog", "onAttach: ClassCastException: " + e.getMessage());
}
return null;
}
}
The Fragment:
public class ExerciseRoutine extends Fragment implements ExerciseRoutine_Dialog.RoutineDialogListener{
public static final String ROUTINE_DIALOG = "Routine Dialog";
private ArrayList<ExerciseRoutine_Information> routineInformations = new ArrayList<>();
private RecyclerView.Adapter adapter;
public static ExerciseRoutine instance(){
return new ExerciseRoutine();
}
#Override
public void sendInput(String name, String split) {
routineInformations.add(new ExerciseRoutine_Information(name, split));
adapter.notifyItemInserted(routineInformations.size());
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.exercise_routine_fragment, container, false);
setHasOptionsMenu(true);
RecyclerView recyclerView = view.findViewById(R.id.ExerciseRoutine_Recycler);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getActivity());
recyclerView.setLayoutManager(layoutManager);
adapter = new ExerciseRoutineAdapter(getContext(), routineInformations);
// So far you have a RecyclerView with an empty List.
recyclerView.setAdapter(adapter);
return view;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.exercise_routine_menu, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()){
case R.id.action_addRoutine:
showDialog();
return true;
}
return super.onOptionsItemSelected(item);
}
private void showDialog(){
ExerciseRoutine_Dialog routineDialog = new ExerciseRoutine_Dialog();
routineDialog.setTargetFragment(ExerciseRoutine.this, 1);
routineDialog.show(getFragmentManager(), ROUTINE_DIALOG);
}
}
Application
I am building an Android application wherein order for certain item will be taken from customers. These order can have 4 different status : Pending, Confirmed, Completed and Cancelled. I have written necessary firebase rule for the same.
My initial design was listing up all the orders in single activity, HomeActivity, and everything looked good. But then I decided to change the design to tabs and viewpager for each status of the order in the same HomeActivity. I am using one of the best library, smart tab layout to generate my views.
Current Design
I planned to keep one single Fragment and update the adapter of recyclerview accordingly.
HomeActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
Global.replaceFragmentWithAnimation(new HomeTabFragment(), getSupportFragmentManager(), R.id.frame_container);
}
Global.replaceFragmentWithAnimation is a static method which just replaces the fragment to necessary container.
HomeTabFragment.java
public class HomeTabFragment extends Fragment implements SmartTabLayout.TabProvider {
public HomeTabFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home_tab, container, false);
final ViewPager viewPager = view.findViewById(R.id.viewpager);
final SmartTabLayout viewPagerTab = view.findViewById(R.id.viewpagertab);
viewPagerTab.setCustomTabView(this);
FragmentPagerItems pages = new FragmentPagerItems(getContext());
pages.add(FragmentPagerItem.of("Pending", OrderListsFragment.class));
pages.add(FragmentPagerItem.of("Confirmed", OrderListsFragment.class));
pages.add(FragmentPagerItem.of("Cancelled", OrderListsFragment.class));
pages.add(FragmentPagerItem.of("Completed", OrderListsFragment.class));
FragmentStatePagerItemAdapter adapter = new FragmentStatePagerItemAdapter(
getActivity().getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);
viewPager.setCurrentItem(0);
viewPagerTab.setViewPager(viewPager);
viewPagerTab.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
}
});
return view;
}
#Override
public View createTabView(ViewGroup container, int position, PagerAdapter adapter) {
LayoutInflater inflater = LayoutInflater.from(container.getContext());
View tab = inflater.inflate(R.layout.custom_tab_icon_and_notification_mark, container, false);
TextView txtTab = tab.findViewById(R.id.txtTitle);
switch (position) {
case 0:
txtTab.setText(getResources().getString(R.string.pending_tab_text));
break;
case 1:
txtTab.setText(getResources().getString(R.string.confirmed_tab_text));
break;
case 2:
txtTab.setText(getResources().getString(R.string.cancelled_tab_text));
break;
case 3:
txtTab.setText(getResources().getString(R.string.completed_tab_text));
break;
default:
throw new IllegalStateException("Invalid pos - " + position);
}
return tab;
}
}
and finally this is my OrderListFragment
OrderListFragment.java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_order_lists, container, false);
recycler_view_order_list = view.findViewById(R.id.recycler_view_order_list); //declared outside
emptyView = view.findViewById(R.id.empty_view);
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
recycler_view_order_list.setItemAnimator(new DefaultItemAnimator());
recycler_view_order_list.setLayoutManager(mLayoutManager);
loadOrders();
return view;
}
public void loadOrders() {
if (recycler_view_order_list != null) {
recycler_view_order_list.setHasFixedSize(true);
}
final DatabaseReference orderTableRef = Global.getDatabase().getReference(Constants.ORDERS_TABLE);
final DatabaseReference customerRef = Global.getDatabase().getReference(Constants.CUSTOMERS_TABLE);
orderTableRef.keepSynced(true);
customerRef.keepSynced(true);
final Query orderList = orderTableRef.orderByChild("status").equalTo("Pending");
//I hardcoded Pending value to make sure everything looks good.
Global.adapter = new FirebaseRecyclerAdapter<Order, OrderItemHolder>(
Order.class,
R.layout.order_item_view,
OrderItemHolder.class,
orderList
) {
#Override
protected void populateViewHolder(final OrderItemHolder viewHolder, final Order model, final int position) {
if(model.getCustId()!=null) {
customerRef.child(model.getCustId()).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
Customers customers = dataSnapshot.getValue(Customers.class);
//set Customer name
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
if ("Pending".equals(model.getStatus())) {
//Some UI updates for Pending
}
if ("Completed".equals(model.getStatus())) {
//Some UI updates for Pending
}
if ("Cancelled".equals(model.getStatus())) {
//Some UI updates for Pending
}
if ("Confirmed".equals(model.getStatus())) {
//Some UI updates for Pending
}
}
}
};
orderTableRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (!dataSnapshot.hasChildren())
emptyView.setVisibility(View.VISIBLE);
else
emptyView.setVisibility(View.GONE);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
emptyView.setVisibility(View.GONE);
}
#Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
if (Global.adapter.getItemCount() == 0)
emptyView.setVisibility(View.VISIBLE);
else
emptyView.setVisibility(View.GONE);
}
};
Global.adapter.registerAdapterDataObserver(mObserver);
recycler_view_order_list.setAdapter(Global.adapter);
}
Very long code but I've shortened it as much as possible. I've couple of problems here.
The data never gets displayed even though it is fetched in orderList. when debugged, it never hits populateViewHolder of FirebaseRecyclerAdapter. When I did some research on this issue, All I found was to add RecyclerView.AdapterDataObserver and register it to Global.adapter which I've already implemented. I am still confused as in why the data is not fetched. addListenerForSingleValueEvent for orderTableRef hits anyhow and if (!dataSnapshot.hasChildren()) condition within evaluates to false thus hiding emptyView message. Few suggestions from GitHub issue also stated to remove recycler_view_order_list.setHasFixedSize(true); but it did not help either.
How can I pass different order status for different tabs? I tried doing below within:
HomeTabFragment.java
Bundle bundle=new Bundle();
bundle.putString("orderType","Pending");
pages.add(FragmentPagerItem.of("Pending", OrderListsFragment.class,bundle));
bundle.putString("orderType","Confirmed");
pages.add(FragmentPagerItem.of("Confirmed", OrderListsFragment.class,bundle));
bundle.putString("orderType","Cancelled");
pages.add(FragmentPagerItem.of("Cancelled",OrderListsFragment.class,bundle));
bundle.putString("orderType","Completed");
pages.add(FragmentPagerItem.of("Completed", OrderListsFragment.class,bundle));
Overriding FragmentPagerItems's add method and passing budnle with different value for same key but then it ended up passing last value i.e. Completed during every initialization.
Note - FragmentPagerItems is an utility extension available with the tabs library used.
Could anyone point me in the right direction here?
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>