I'm writing an Android application with ViewPager. My ViewPager contains two Fragments. In each fragment are located two receclerViews to show different LiveData lists of items from Database (I'm using Room)
The code for BaseFragment
public class BaseFragment extends Fragment {
private RecyclerView recyclerView;
private NewsAdapter adapter;
public ViewModel mViewModel;
public BaseFragment() {
// Required empty public constructor
}
#Override
public void onCreate(#Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(ViewModel.class);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View mView = inflater.inflate(R.layout.fragment_feeds, container, false);
recyclerView = mView.findViewById(R.id.feeds_recycler_view);
return mView;
}
#Override
public void onActivityCreated(#Nullable final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setUpRecyclerView();
mViewModel.getAllNewsEntities()
.observe(this, allNewsEntities -> {
NewsEntityUtilCallback productDiffUtilCallback =
new NewsEntityUtilCallback(adapter.getNewsItems(), allNewsEntities);
DiffUtil.DiffResult newsEntitiesDiffResult = DiffUtil.calculateDiff(productDiffUtilCallback);
adapter.setNewsItems(allNewsEntities);
newsEntitiesDiffResult.dispatchUpdatesTo(adapter);
});
}
protected void setUpRecyclerView() {
LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
recyclerView.setLayoutManager(layoutManager);
ItemClickListener onClickListener = (v, position) -> {
String url = adapter.getNewsItems().get(position).getLink();
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
if (browserIntent.resolveActivity(getContext().getPackageManager()) != null) {
startActivity(browserIntent);
}
};
adapter = new NewsAdapter(getContext(), onClickListener);
recyclerView.setAdapter(adapter);
}
}
Look closely at this code section:
mViewModel.getAllNewsEntities()
.observe(this, allNewsEntities -> {
NewsEntityUtilCallback productDiffUtilCallback =
new NewsEntityUtilCallback(adapter.getNewsItems(), allNewsEntities);
DiffUtil.DiffResult newsEntitiesDiffResult = DiffUtil.calculateDiff(productDiffUtilCallback);
adapter.setNewsItems(allNewsEntities);
newsEntitiesDiffResult.dispatchUpdatesTo(adapter);
});
}
Here I'm going to observe different LiveData queries in each Fragment(in the second Fragment it's mViewModel.getAllBookmarkedNewsEntities()).The other things my code will be equals (the same lifecycles methods, the same RecyclerView). So could give me advice about the best possible design principle to refactor my code. I don't want simply to copy my code in another Fragment class just because of one line
Just extends the BaseFragment and override onActivityCreated method. Something like this:
public class ReuseBaseFragment extends BaseFragment {
#Override
public void onActivityCreated(#Nullable final Bundle savedInstanceState) {
//Your new code here
}
}
Related
I have 2 fragment in activityty a list fragment and details fragment,details fragment showing selected items details from list fragment and there is button to change list item "status" set order as ready.
I want to move selected item to ready seaction when button Order is ready clicked.
I tried it with observing with shared view model but onchange method not calling when I set value in it.
here is a viewModel:
package com.example.ordermanager.fragments;
import android.database.ContentObserver;
import android.os.Handler;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import com.example.ordermanager.fragments.orderlist.dummy.DummyContent;
import java.util.List;
public class SharedViewModel extends ViewModel {
private MutableLiveData<DummyContent.DummyItem> item = new MutableLiveData<DummyContent.DummyItem>();
public void setItem(DummyContent.DummyItem value){
item.setValue(value);
}
public MutableLiveData<DummyContent.DummyItem> getItem(){
return item;
};
}
ListFragment:
public class OrderItemFragment extends Fragment {
// TODO: Customize parameter argument names
private static final String ARG_COLUMN_COUNT = "column-count";
// TODO: Customize parameters
private int mColumnCount = 1;
private OnListFragmentInteractionListener mListener;
private SharedViewModel vm;
private RecyclerView recyclerView;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public OrderItemFragment() {
}
// TODO: Customize parameter initialization
#SuppressWarnings("unused")
public static OrderItemFragment newInstance(int columnCount) {
OrderItemFragment fragment = new OrderItemFragment();
Bundle args = new Bundle();
args.putInt(ARG_COLUMN_COUNT, columnCount);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mColumnCount = getArguments().getInt(ARG_COLUMN_COUNT);
}
vm = ViewModelProviders.of(this).get(SharedViewModel.class);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_order_item_list, container, false);
// Set the adapter
if (view instanceof RecyclerView) {
Context context = view.getContext();
recyclerView = (RecyclerView) view;
if (mColumnCount <= 1) {
recyclerView.setLayoutManager(new LinearLayoutManager(context));
} else {
recyclerView.setLayoutManager(new GridLayoutManager(context, mColumnCount));
}
recyclerView.setAdapter(new MyOrderItemRecyclerViewAdapter(DummyContent.ITEMS, mListener));
}
return view;
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Observer<DummyItem> itemObserver = new Observer<DummyItem>() {
#Override
public void onChanged(#Nullable DummyItem selectedItem) {
//this never happening
Log.e("hereeeee","dfgdfg");
recyclerView.getAdapter().notifyDataSetChanged();
}
};
vm.getItem().observe(this, itemObserver);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnListFragmentInteractionListener) {
mListener = (OnListFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnListFragmentInteractionListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
* <p/>
* See the Android Training lesson <a href=
* "http://developer.android.com/training/basics/fragments/communicating.html"
* >Communicating with Other Fragments</a> for more information.
*/
public interface OnListFragmentInteractionListener {
// TODO: Update argument type and name
void onListFragmentInteraction(DummyItem item);
}
}
DetailsFragment:
public class OrderDetailFragment extends Fragment {
private SharedViewModel mViewModel;
private DummyContent.DummyItem selectedItem;
private Button ReadyBtn;
public static OrderDetailFragment newInstance() {
return new OrderDetailFragment();
}
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.order_detail_fragment, container, false);
Bundle bundle = getArguments();
if(bundle != null){
selectedItem = (DummyContent.DummyItem)getArguments().getSerializable("item");
TextView tv = (TextView) view.findViewById(R.id.detailid);
tv.setText(selectedItem.content);
}
ReadyBtn = view.findViewById(R.id.readyBtn);
ReadyBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(selectedItem != null){
selectedItem.isReady = true;
mViewModel.getItem().setValue(selectedItem);
}
}
});
return view;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(SharedViewModel.class);
}
}
Observer is in ListFragment OnViewCreated function
Any Ideas?
You should change the data in your adapter before calling notifyDataSetChanged() method. Now you are getting new value in itemObserver but you're not changing the adapter.
UPD. I've solved the problem! The key in your initialization code of SharedViewModel. You should attach activity to the ViewModelProviders class in both cases, but you use this and in reality you have two different instances instead of one which should be attached to the parent activity. So, change the code of initialization to
mViewModel = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); and it'll work!
When you declare local variables in functions, they get destroyed when the function call ends. Therefore you need to store your itemObserver in a field.
On a side note...
You don't need a default empty constructor in fragments unless you create a custom one, which isn't recommended.
Regarding recyclerview I would recommend reading this in detail (especially the ListAdapter part).
This question already has answers here:
Get application context from non activity singleton class
(3 answers)
Closed 4 years ago.
I can't click RecyclerView to a new Activity from RecyclerViewAdapter.
I call ItemClick here.
DayAdapter.java:
holder.setItemClickListener(new ItemClickListener() {
#Override
public void onClick(View view, int position, boolean isLongClick) {
openProgramActivity(view, position);
}
});
}
This function opens a new Activity:
public void openProgramActivity(View view, int position) {
//Intent openProgramActivity = new Intent(context, ProgramActivity.class);
Intent openProgramActivity = new Intent(view.getContext(), ProgramActivity.class);
openProgramActivity.putExtra("index",position);
view.getContext().startActivity(openProgramActivity);
}
FragmentDay30.java:
public class FragmentDay30 extends Fragment {
private View view;
public static FragmentDay30 newInstance() {
FragmentDay30 fragment = new FragmentDay30();
Bundle args = new Bundle();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_30day, container,false);
ViewPager slideViewPager = (ViewPager) view.findViewById(R.id.slideViewPager);
SlideAdapter slideAdapter = new SlideAdapter(getActivity());
slideViewPager.setAdapter(slideAdapter);
RecyclerView fragment30datRecyclerView = (RecyclerView) view.findViewById(R.id.fragment30dayRecyclerView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity(),LinearLayoutManager.VERTICAL,false);
fragment30datRecyclerView.setLayoutManager(linearLayoutManager);
DayAdapter dayAdapter = new DayAdapter(getActivity());
fragment30datRecyclerView.setAdapter(dayAdapter);
return view;
}
I try to use getActvity() and getContext() but not to new Activity.
Pass contaxt to recyclerview adapter constructor like this
Context context;
MyAdapter(Context context, .....){
this.context=context;
}
Call Activity
context.startActivity(......);
Though you can start Activity from Adapter class passing a Context but as Documented it's
not a Good design pattern
and also a Bad practice to follow.
I would rather suggest to have an interface defined in your Adapter class which would be implemented by the Fragment class. Fragment class initializes the Adapter passing it's reference which you would typeCast to interface like this
DayAdpater.class
public class DayAdapter extends RecyclerView.Adapter<DayAdapter.ViewHolder> {
private OnActionListener mListener;
DayAdapter(OnActionListener listener){
this.mListener=listener;
}
holder.setItemClickListener(new ItemClickListener() {
#Override
public void onClick(View view, int position, boolean isLongClick) {
mListener.startActivity(position);
}
});
interface OnActionListener{
public void startActivity(int position);
}
}
FragmentDay30.class
public class FragmentDay30 extends Fragment implements DayAdapter.OnActionListener{
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_30day, container,false);
RecyclerView fragment30datRecyclerView = (RecyclerView) view.findViewById(R.id.fragment30dayRecyclerView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity(),LinearLayoutManager.VERTICAL,false);
fragment30datRecyclerView.setLayoutManager(linearLayoutManager);
DayAdapter dayAdapter = new DayAdapter(getActivity(), this);
fragment30datRecyclerView.setAdapter(dayAdapter);
return view;
}
/**
* this is the place where you should start a new activity
*/
public void startActivity(int position) {
//Intent openProgramActivity = new Intent(context, ProgramActivity.class);
Intent openProgramActivity = new Intent(getActivity(), ProgramActivity.class);
openProgramActivity.putExtra("index",position);
getActivity.startActivity(openProgramActivity);
}
}
This is how the your adpater class interacts with the fragment class.
Hope this helps.
Pass context in RecyclerView adapter constructor which you are using for setAdapter like this:
Context context;
MyCustomAdapter(Context context, .....){
this.context=context;
}
For call Activity used:
context.startActivity(......);
I am the beginner in android
I have found many answers of a similar question but no answer unfortunately worked for me
I am using recylcerView using card view to show student data .I am taking Data from My sqlite db.
My code works fine in activity but because of navigation drawer i put it in a fragment and it is showing blank.
here is my activity and fragment code.
MainActivity
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private PersonDBHelper dbHelper;
private MyCustomAdapter adapter;
private String filter = "";
TextView textView;
public static int navItemIndex = 0;
Toolbar toolbar;
private DrawerLayout mDrawerLayout;
FloatingActionButton button;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.person_list);
/* toolbar=findViewById(R.id.toolbar);
setSupportActionBar(toolbar);*/
mDrawerLayout=findViewById(R.id.drawer_layout);
NavigationView navigationView=findViewById(R.id.nav_view);
mRecyclerView = (RecyclerView)findViewById(R.id.recycler_view);
textView=findViewById(R.id.empty_view);
mLayoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(mLayoutManager);
//populate recyclerview
populaterecyclerView(filter);
button=findViewById(R.id.fab_add);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
goToAddUserActivity();
}
});
}
private void populaterecyclerView(String filter) {
dbHelper = new PersonDBHelper(this);
adapter = new MyCustomAdapter(dbHelper.peopleList(filter), this, mRecyclerView);
mRecyclerView.setAdapter(adapter);
}
private void goToAddUserActivity(){
Intent intent = new Intent(MainActivity.this, AddRecordActivity.class);
startActivity(intent);
}
#Override
protected void onResume() {
super.onResume();
adapter.notifyDataSetChanged();
}
this is working fine but for the fragment i did following changes
StudentListFragment()
ublic class StudentListFragment extends Fragment {
private RecyclerView mRecyclerView;
private RecyclerView.LayoutManager mLayoutManager;
private PersonDBHelper dbHelper;
private MyCustomAdapter adapter;
FloatingActionButton button;
private String filter = "";
View rootView;
#RequiresApi(api = Build.VERSION_CODES.M)
public static StudentListFragment newInstance(){
StudentListFragment studentListFragment = new StudentListFragment();
Bundle args=new Bundle();
studentListFragment.setArguments(args);
return studentListFragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
rootView=inflater.inflate(R.layout.person_list,container,false);
mRecyclerView=rootView.findViewById(R.id.recycler_view);
dbHelper = new PersonDBHelper(getContext());
adapter = new MyCustomAdapter(dbHelper.peopleList(filter), getContext(), mRecyclerView);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
button=rootView.findViewById(R.id.fab_add);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
goToAddUserActivity();
}
});
return rootView;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(false);
ensureList();
}
private void goToAddUserActivity() {
Intent intent = new Intent(getActivity(), AddRecordActivity.class);
startActivity(intent);
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getActivity().setTitle("StudentList");
}
public void ensureList(){
if (dbHelper != null) {
return;
}
View root = getView();
if (root == null) {
throw new IllegalStateException("Content view not yet created");
} }
}
i am calling this fragment in navigation drawer code is working but not displaying any data. If i put this fragment first in navigation drawer menu means when navigation drawer activity starts this fragment will get open.In such case it is displaying my cards(my data) but if i switch to other fragment and return back to this fragment it is showing blank
In fragment, onCreateView() method, please inflate your root view and return it immediately, do not do anything else.
Implement your logic to onViewCreated(). something like this :
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
rootView=inflater.inflate(R.layout.person_list,container,false);
return rootView;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
getActivity().setTitle("StudentList");
mRecyclerView=view.findViewById(R.id.recycler_view);
dbHelper = new PersonDBHelper(getContext());
adapter = new MyCustomAdapter(dbHelper.peopleList(filter), getContext(), mRecyclerView);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setAdapter(adapter);
adapter.notifyDataSetChanged();
button=view.findViewById(R.id.fab_add);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
goToAddUserActivity();
}
});
}
could you please try this. And make sure you have items in the list.
I am trying to load the data list asynchronously and showing it on RecyclerView in fragments. However, the same implementation works for Activity class but fails for Fragment:
public class ItemThreeFragment extends Fragment {
String email;
private CompositeSubscription mSubscriptions;
HistoryAdapter historyAdapter;
RecyclerView recyclerView;
public static ItemThreeFragment newInstance() {
ItemThreeFragment fragment = new ItemThreeFragment();
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
email = ((BaseApplication) getActivity().getApplication()).getEmail();
mSubscriptions = new CompositeSubscription();
getUserHistory(email);
}
private void getUserHistory(String email) {
mSubscriptions.add(NetworkUtil.generic().getUserCheckinInfo(email)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleCheckInInfoResponse,this::handleError));
}
private void handleCheckInInfoResponse(List<Checkin> checkins) {
System.out.println(checkins);
historyAdapter = new HistoryAdapter(this.getActivity(), checkins);
recyclerView.setAdapter(historyAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
}
private void handleError(Throwable throwable) {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_item_three, container, false);
recyclerView = (RecyclerView) rootView.findViewById(R.id.historyList);
return rootView;
}
}
Any kind of suggestions will be highly appreciated.
try to set the adapter in onCreateView() instead of onCreate.
Check for data
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
recyclerView.setAdapter(historyAdapter);
Use these two lines onCreateView after you have fetched the data.
I am using ViewPager in my Android project, and I use FragmentStatePagerAdapter to set the pages.
class MyPageAdapter extends FragmentStatePagerAdapter {
List<Fragment> mList = new ArrayList<>();
public MyPageAdapter(FragmentManager fm) {
super(fm);
this.init();
}
private void init() {
mList.add(new FragmentOne());
mList.add(new FragmentTwo());
mList.add(new FragmentThree());
....
}
#Override
public Fragment getItem(int position) {
return mList.get(position);
}
}
And for each Fragment data will be loaded from the server once the view created, like this:
public class FragmentOne extends Fragment {
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(getViewResourceId(), container, false);
ButterKnife.bind(this, v);
setRetainInstance(true);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
mRecycleView.setLayotManager(linearLayoutManager);
endlessRecyclerViewScrollListener = new EndlessRecyclerViewScrollListener((LinearLayoutManager) mRecycleView.getLayoutManager()) {
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
loadByPage(page);
}
};
mRecycleView.addOnScrollListener(endlessRecyclerViewScrollListener);
return v;
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = createAdapter();
mRecycleView.setAdapter(mAdapter);
loadByPage(1); // load data from server
}
}
As shown, the Fragment contains a endless recyclerview.
So far so good. However once I change the selected view pageļ¼ I found that the data will be reloaded every time. For example, I have scroll 3 pages in FragmentOne, and then change to FragmentTwo, and when I change to FragmentOne back, FragmentOne will try load data of page 1.
Is it possible to avoid this?
you can do something like this :
private boolean isDataCalled = true;
private List<ItemModel> itemModelList;
and then
if (isDataCalled){
loadByPage(1); // Initialize itemModelList in this method
setItemAdapter(itemModelList);
isDataCalled = false;
}else {
setItemAdapter(itemModelList);
}
setItemAdapter method
private void setItemAdapter(List<ItemModel> itemModelList){
mAdapter = createAdapter(itemModelList);
mRecycleView.setAdapter(mAdapter);
}