I have an activity HomeActivity where I navigate between two fragments Fragment1 and Fragment2. The Fragment1 fragment contains a recyclerview. When I move to Fragment2, Fragment1 is paused and stopped. When I come back to Fragment1, the list loads again. How to return to the scroll position from which I left? One way which I have tried is to save the state of recyclerview in a bundle before the fragment is paused in onPause() method. Now, how do I use this save state when view is created?
The navigation code is:
final NavController navController = Navigation.findNavController(this, R.id.nav_controller);
binding.go_to_fragment2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
navController.navigate(R.id.fragment2);
}
});
binding.go_to_fragment1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
navController.navigate(R.id.fragment1);
}
});
The code for Fragment1 is:
public class Friends extends Fragment1 {
private FriendsFragmentBinding binding;
private MyAdapter myAdapter;
private static Bundle RecyclerState;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
binding = FriendsFragmentBinding.inflate(inflater, container, false);
binding.mRecycler.setHasFixedSize(true);
binding.mRecycler.setLayoutManager(new LinearLayoutManager(getContext()));
PagedList.Config config = new PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setPrefetchDistance(2)
.setPageSize(5)
.build();
// Init Adapter Configuration
Query mQuery = FirebaseFirestore.getInstance().collection("posts");
FirestorePagingOptions<Post> options = new FirestorePagingOptions.Builder<Post>()
.setLifecycleOwner(this)
.setQuery(mQuery, config, Post.class)
.build();
binding.swipe.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
myAdapter.refresh();
}
});
myAdapter = new MyAdapter(options, new MyAdapter.GetState() {
#Override
public void thisState(boolean state) {
if (state) {
binding.swipe.setRefreshing(true);
} else {
binding.swipe.setRefreshing(false);
}
}
});
binding.mRecycler.setAdapter(myAdapter);
return binding.getRoot();
}
#Override
public void onPause() {
super.onPause();
Parcelable listState = Objects.requireNonNull(binding.mRecycler.getLayoutManager()).onSaveInstanceState();
RecyclerState = new Bundle();
RecyclerState.putParcelable("key", listState);
}
}
You're calling binding.mRecycler.setAdapter(myAdapter); before your adapter has any data set. It is when your adapter is set that the saved scroll position is restored. Since there's no items in your adapter, there's nothing to scroll back to, so your scroll position is lost.
As per the Restore RecyclerView scroll position blog post, you have two options:
Don't set your adapter until you get data for the first time.
Upgrade to RecyclerView 1.2.0-alpha02 or higher and switch the StateRestorationPolicy to PREVENT_WHEN_EMPTY:
adapter.stateRestorationPolicy = PREVENT_WHEN_EMPTY
Which will mean that the RecyclerView will wait until your adapter is populated with data before restoring its state, thereby restoring your scroll position correctly.
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 am developing an app which has navigation drawer which calls RecyclerView.Adapter. RecyclerView.Adapter has list of schedules. I have footer in RecyclerView which enabled user to create new schedule.
public class MainActivity extends AppCompatActivity {
private DrawerLayout mDrawerLayout;
protected void onCreate(Bundle savedInstanceState) {
…
}
public void selectItemDrawer(MenuItem menuItem){
Fragment myFragment = null;
Class fragmentClass;
switch (menuItem.getItemId()) {
case R.id.menu_home:
fragmentClass = dashboardFragment.class;
break;
case R.id.menu_plans:
fragmentClass = ScheduleListFragment.class;
break;
default:
fragmentClass = dashboardFragment.class;
}
try {
myFragment = (Fragment) fragmentClass.newInstance();
}
catch (Exception e) {
e.printStackTrace();
}
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.flContent,myFragment).commit();
menuItem.setCheckable(true);
setTitle(menuItem.getTitle());
mDrawerLayout.closeDrawers();
}
}
Adapter Class
public class MyRecyclerAdapter extends Adapter<RecyclerAdapter.ViewHolder> {
private ArrayList<ScheduleModel> activeSchedules;
private ShowScheduleListener mListener;
public MyRecyclerAdapter(Context ctx, ShowScheduleListener listener){
mListener = listener;
...
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.itemView.setOnClickListener(new OnClickListener(){
#Override
public void onClick(View v) {
mListener.onItemClicked(mItems[position]);
}
});
}
interface ShowScheduleListener{
void onItemClicked(ItemType item);
}
}
}
RecyclerView Fragment
public class ScheduleList extends Fragment implements MyRecyclerAdapter.ShowScheduleListener {
View v;
private RecyclerView scheduleRecyclerView;
private ArrayList<ScheduleModel> activeSchedules;
private DatabaseHandler databaseHandler;
public ScheduleList() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
v = inflater.inflate(R.layout.fragment_schedule_list,container,false);
scheduleRecyclerView=v.findViewById(R.id.RecyclerView_schedule_list);
scheduleRecyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
activeSchedules = new ArrayList<>();
databaseHandler=DatabaseHandler.getInstance(getContext());
activeSchedules=databaseHandler.getActiveSchedules();
return v;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
MyRecyclerAdapter myRecyclerAdapter = new MyRecyclerAdapter (getContext(), activeSchedules);
scheduleRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
scheduleRecyclerView.setAdapter(myRecyclerAdapter);
}
#Override
public void onScheduleItemClick(int position) {
// as per other posts I am supposed to initiate Fragment here
// but should fragment talk to another fragment via interface implemented this way?
}
I have 2 questions
Is it a good architecture to implement Navigation drawer -> Fragment to show existing schedules with RecycleView -> Fragment to show selected schedule details?
How to pass information to fragment from RecycleView?. I know communication between fragment could be achieved using interface but wanted to check if there is any other way to implement communication between RecycleView and Fragment?
Is it a good architecture to implement Navigation drawer -> Fragment to show existing schedules with RecycleView -> Fragment to show selected schedule details?
That is how the default calendar app on android works. So, yes, that's a good architecture. The default calendar app has a nav drawer. Clicking the drawer's "Schedule" item opens up the Schedule fragment with a list of events. Clicking on one of the events opens up the detail screen for that particular event.
How to pass information to fragment from RecycleView?. I know communication between fragment could be achieved using interface but wanted to check if there is any other way to implement communication between RecycleView and Fragment?
Use bundles: How to transfer some data to another Fragment?
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 have one MainActivity and two fragments. In FragmentA I have a recycler view. If I click on "add" button there, the FragmentB is open. The thing I would like to is to write text into some EditTexts and send data back to FragmentA (and render that data in the recycler view). Could you suggest me something please? Thanks
FragmentB
public class NewContactFragment extends Fragment {
EditText name, number, email;
public String mName;
public String mNumber;
public String mEmail;
boolean isFavourite = false;
public NewContactFragment() {
// Required empty public constructor
}
public static NewContactFragment newInstance() {
NewContactFragment fragment = new NewContactFragment();
Bundle bundle = new Bundle();
fragment.setArguments(bundle);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
setHasOptionsMenu(true);
super.onCreate(savedInstanceState);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
//set title
((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.new_contact);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_new_contact, container, false);
name = (EditText) view.findViewById(R.id.ed_name);
number = (EditText) view.findViewById(R.id.ed_number);
email = (EditText) view.findViewById(R.id.ed_email);
mName = name.getText().toString();
mNumber = number.getText().toString();
mEmail = email.getText().toString();
return view;
}
#Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.new_contact_menu, menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_done:
//TODO: save editTexts and return to ContactListFragment
break;
case R.id.action_favourite:
getActivity().invalidateOptionsMenu();
//Toast.makeText(getContext(), "isFavourite is: " + isFavourite, Toast.LENGTH_SHORT).show();
break;
}
return super.onOptionsItemSelected(item);
}
FragmentA
public class ContactListFragment extends Fragment implements View.OnClickListener {
private static final String TAG = "newcontact";
FloatingActionButton fabButton;
SearchView searchView;
RecyclerView recyclerView;
ContactsAdapter contactsAdapter;
List<Contact> mContact = new ArrayList<>();
public static ContactListFragment newInstance() {
Bundle args = new Bundle();
ContactListFragment fragment = new ContactListFragment();
fragment.setArguments(args);
return fragment;
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_contact_list, container, false);
searchView = (SearchView) view.findViewById(R.id.search_view);
fabButton = (FloatingActionButton) view.findViewById(R.id.fab_button);
fabButton.setOnClickListener(this);
recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
mContact = SugarRecord.listAll(Contact.class);
contactsAdapter = new ContactsAdapter(getActivity(), mContact);
recyclerView.setAdapter(contactsAdapter);
inputFilter();
return view;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
//show actionBar
((MainActivity) getActivity()).getSupportActionBar().show();
//show title
((MainActivity) getActivity()).getSupportActionBar().setTitle(R.string.app_name);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
public void inputFilter() {
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
#Override
public boolean onQueryTextSubmit(String query) {
return false;
}
#Override
public boolean onQueryTextChange(String newText) {
contactsAdapter.filterList(newText);
return true;
}
});
}
#Override
//Fab button listener
public void onClick(View v) {
((MainActivity) getActivity()).showFragment(new NewContactFragment(), TAG);
}
Fragments should generally only communicate with their direct parent activity. Fragments communicate through their parent activity allowing the activity to manage the inputs and outputs of data from that fragment coordinating with other fragments or activities. Think of the Activity as the controller managing all interaction with each of the fragments contained within.
A few exceptions to this are dialog fragments presented from within another fragment or nested child fragments. Both of these cases are situations where a fragment has nested child fragments and that are therefore allowed to communicate upward to their parent (which is a fragment).
The important thing to keep in mind is that fragments should not directly communicate with each other and should generally only communicate with their parent activity. Fragments should be modular, standalone and reusable components. The fragments allow their parent activity to respond to intents and callbacks in most cases.
There are three ways a fragment and an activity can communicate:
Bundle - Activity can construct a fragment and set arguments
Methods - Activity can call methods on a fragment instance
Listener - Fragment can fire listener events on an activity via an interface
In other words, communication should generally follow these principles:
Activities can initialize fragments with data during construction
Activities can pass data to fragments using methods on the fragment instance
Fragments can communicate up to their parent activity using an interface and listeners
Fragments should pass data to other fragments only routed through their parent activity
Fragments can pass data to and from dialog fragments
Fragments can contain nested child fragments
Read more about Fragment and its communication at Creating and Using Fragments