I have an application (Java) which originally had 5 sections. All of these are fragments and they are accessed by 5 icons in the bottom navigation. However, one of the sections (Lessons) is now being replaced by a different fragment which contains a recycler view. Under this new design, one of the items in the recycler view will call the Lessons fragment.
The question is, how can I call the Lessons fragment from within the Recycler View contained in the new fragment?
Usually inside a recycler view I'd use code like the following:
#Override
public void onClick(View v) {
int index = getAdapterPosition();
LessonItem item = lessonList.get(index);
Intent intent = new Intent(context, SomeActivity.class);
Bundle bundle = new Bundle();
bundle.putSerializable("lesson", item);
intent.putExtras(bundle);
((Activity) context).startActivityForResult(intent, 1);
}
but in this case I'm not dealing with an activity. Is there a way I can accomplish this without having to convert the fragment to an activity?
As ismailfarisi mentioned before, if you use Navigation Component, it makes the transition easier.
However, if you want to still manage fragment replacements manually, you can interfaces as callback.
interface LessonItemListener {
void onItemClick(LessonItem item);
}
in your recycler view adapter
class YourAdapter {
public YourAdapter(LessonItemListener listener) {
this.listener = listener;
}
private LessonItemListener listener;
...
#Override
public void onClick(View v) {
int index = getAdapterPosition();
LessonItem item = lessonList.get(index);
listener.onItemClick();
}
}
in your fragment has recyclerview
class YourFragment {
private LessonItemListener listener;
public void setListener(LessonItemListener listener) {
this.listener = listener;
}
private void setupRecyclerView() {
YourAdapter adapter = new YourAdapter(listener);
recyclerView.setAdapter(adapter);
}
}
in your Activity contains those fragments
class YourActivity extends AppCompatActivity implements LessonItemListener {
private void setupFragments() {
YourFragment fragment = new YourFragment();
fragment.setListener(this);
}
...
#Override
public void onItemClick(LessonItem item) {
//you are in activity now
//make whatever you want here
//such as fragment replacement in your bottom nav
}
}
navigation component from jetpack is better for handling fragment transition
you can check this documentation https://developer.android.com/guide/navigation
Related
I faced a problem with my recyclerview and fragments . It made me crazy . My English is not so good but I try to explain it.
I have a view pager with 2 different fragments and these two fragments load a same fragments with different data . this inner fragment load data to a recyclerview .
loading data run on inner fragment OnResume() method (first checked if adapter is null then load data to recyclerview . )
The problem is here when I clicked on recyclerview items it start new activity and when I back from activity to fragment recyclerview jump to top of the list . However it is not loading new data .
I tried to save recyclerview state on onPause method and restore it on onResume but it is not work . if I do it with delay it works but first it jump to top and then back to last item and it is not good at all .bellow code
/* new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// if(state !=null)
recyclerViewFragmentChannel.getLayoutManager().onRestoreInstanceState(mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE));
}
}, 1000);*/
I am wondering if anybody can help me to overcome with this problem .I want to prevent recyclerview jump to top. I attached my inner fragment codes to make my explain more clear .
my inner fragment :
public class Fragment_Channels extends Fragment {
private static final String ARG_COUNT = "param1";
private Integer counter;
private Adapter adapter;
private View view;
private List<Fragment> fragmentsList;
private RecyclerView recyclerViewFragmentChannel;
private GridLayoutManager layoutManagerPortrait, layoutManagerLandScape;
private Parcelable state;
private Bundle mBundleRecyclerViewState;
private String KEY_RECYCLER_STATE = "recycler_state";
public Fragment_Channels() {
// Required empty public constructor
}
public static Fragment_Channels newInstance(Integer counter) {
Fragment_Channels fragment = new Fragment_Channels();
Bundle args = new Bundle();
args.putInt(ARG_COUNT, counter);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentsList = getActivity().getSupportFragmentManager().getFragments();
if (getArguments() != null) {
counter = getArguments().getInt(ARG_COUNT);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (view == null) {
// Inflate the layout for this fragment
view = inflater.inflate(R.layout.fragment_channels, container, false);
recyclerViewFragmentChannel = view.findViewById(R.id.recyclerViewFragmentChannel);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
layoutManagerLandScape = new GridLayoutManager(view.getContext(), 5);
recyclerViewFragmentChannel.setLayoutManager(layoutManagerLandScape);
} else {
layoutManagerPortrait = new GridLayoutManager(view.getContext(), 2);
recyclerViewFragmentChannel.setLayoutManager(layoutManagerPortrait);
}
}
return view;
}
#Override
public void onPause() {
super.onPause();
mBundleRecyclerViewState = new Bundle();
state = layoutManagerPortrait.onSaveInstanceState();
}
#Override
public void onResume() {
super.onResume();
if ((recyclerViewFragmentChannel.getAdapter() == null)) {
//Load data to recyclerview here
} else {
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// if(state !=null)
layoutManagerPortrait.onRestoreInstanceState(mBundleRecyclerViewState.getParcelable(KEY_RECYCLER_STATE));
}
}, 1000);
}
}
If You want to make RecyclerView be in the same position as it was before going to 2nd fragment You can save the actual position in shared preferences. When You back to 1st fragment just load value from shared preferences and use method to scroll to a given position.
Scroll RecyclerView programmatically
recyclerView.smoothScrollToPosition(savedPosition); // if You want smooth scroll
recyclerView.scrollToPosition(savedPosition); // if You want instant scroll
Add android:descendantFocusability="blocksDescendants" in outer or parent layout of recyclerview.
This one is a bit hard to explain and demonstrate. I will try my best.
I have two fragments ItemListFragment and ItemViewFragment : a fragment with a recycleview and listing inside the recycleview and a fragment displaying a single item respectively.
In the ItemListFragment, there is RecyclerViewClickListener to handle clicks on items of the list.
The implementation is as followed:
public class ItemListFragment extends Fragment {
private OnFragmentListClickListener onClickListener = null;
public interface OnFragmentListClickListener {
void OnFragmentListClick(ItemModel Item);
}
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
class RecyclerViewClickListenerImpl implements RecyclerViewClickListener {
#Override
public void onClick(View view, int position) {
if (onClickListener != null) {
onClickListener.OnFragmentListClick(adapter.getItem(position));
}
}
adapter = new ItemListAdapter(getActivity(), ItemModelList, new RecyclerViewClickListenerImpl());
mRecyclerView = getActivity().findViewById(R.id.recycler_view);
RecyclerView.LayoutManager mLayoutManager = new GridLayoutManager(getActivity(), 2);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mRecyclerView.setAdapter(adapter);
.
.
.
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
Toast.makeText(getActivity(), "onAttach", Toast.LENGTH_SHORT).show();
if (context instanceof OnFragmentListClickListener) {
onClickListener = (OnFragmentListClickListener) context;
}
}
.
.
.
}
The list contains thumbnail (loaded with Glide) and a text for each time.
When I add this fragment in my activity implementing the ItemListFragment.OnFragmentListClickListener interface, everything works fine using the code below:
public class MainActivity extends AppCompatActivity
implements ItemListFragment.OnFragmentListClickListener {
.
.
.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ItemListFragment fragment_list = ItemListFragment.newInstance();
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment_list);
fragmentTransaction.commit();
}
#Override
public void OnFragmentListClick(CameraModel camera) {
// handle the list click
}
.
.
.
}
Things get weird when I try to add a second fragment (ItemViewFragment) in my activity as followed:
When there is a second fragment, the RecyclerViewClickListener's onClick on the ItemListFragment is not called anymore:
class RecyclerViewClickListenerImpl implements RecyclerViewClickListener {
#Override
public void onClick(View view, int position) {
if (onClickListener != null) {
onClickListener.OnFragmentListClick(adapter.getItem(position));
}
}
Also, certain thmbnails in the ItemListFragment stopped working when a second fragment has been added in the activity.
This is a weird one... I tried using fragmentTransaction.add instead of fragmentTransaction.add also and all kind of combinations...
You want put your onClick in item of recycle view. You should do every thing of item (like: load image, make event for view,... ) in ViewHolder class. Beacause that's easier to view source code in your activity or fragment. You can references my ViewHolder class with this link. It will help you.
I understand that this is quite a common issue, and I have referred to many different other questions but I still can't get this to work.
My activity implements a view pager with two tabs and in each tab is a listview. I have a adapter for my view pager which links the two fragments and in each fragment, a adapter to link the data to the listview.
In my activity menu, I have a menu which creates an edittext in an alertdialog for me to input new fields into one of the listview in one of the fragment.
My activity (contains a viewpager)
protected void onCreate(Bundle savedInstanceState)
{
...
subAdapter = new SubAdapter(getSupportFragmentManager(), data);
((ViewPager) findViewById(R.id.viewPager)).setAdapter(subGroupAdapter);
}
My viewpager adapter
public class SubAdapter extends FragmentPagerAdapter
{
public SubGroupAdapter(FragmentManager fm, data data)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
Bundle bundle = new Bundle();
bundle.putString("data", data);
switch (position)
{
case 0:
Fragment1 frag1 = new Fragment1();
frag1.setArguments(bundle);
return frag1;
case 1:
Fragment2 frag2 = new Fragment2();
frag2.setArguments(bundle);
return frag2;
}
return null;
}//some other methods below
Fragment1 / Fragment2 (both fragments have a listview)
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
frag1Adapter = new frag1Adapter(this, data);
((ListView) getView().findViewById(R.id.listView)).setAdapter(frag1Adapter);
}
Custom listview adapter for both listviews in both fragments
public class ExpenseAdapter extends BaseAdapter implements OnClickListener
{ ... }
As I mentioned earlier, I can input a new entry into either listview from the activity action bar button. However, the listview does not get updated and I can't reference the listview adapter to call notifydatasetchanged() from the activity.
What is the best way I can proceed from here onwards? thank you so much!
I have tried exploring using interfaces, tags but can't get it to work currently.
What you should do is create a public method for your Fragment1 and Fragment2 like so:
define in your Activity:
Fragment1 frag1;
Fragment2 frag2;
then your ViewPager:
public class SubAdapter extends FragmentPagerAdapter
{
public SubGroupAdapter(FragmentManager fm, data data)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
Bundle bundle = new Bundle();
bundle.putString("data", data);
switch (position)
{
case 0:
frag1 = new Fragment1();
frag1.setArguments(bundle);
return frag1;
case 1:
frag2 = new Fragment2();
frag2.setArguments(bundle);
return frag2;
}
return null;
}
}
Put this method in Fragment1.java :
public void updateFragment1ListView(){
if(adapter != null){
adapter.notifyDataSetChanged();
}
}
and from your activity call:
if(frag1 != null){
frag1.updateFragment1ListView();
}
obviously change the names for your adapter if its not called adapter...
Just do the same for Fragment2.java as well
I would recommend creating an interface. Here's an Example:
public interface UpdateListFragmentInterface {
void updateList();
}
Then in the Activity, add the delegate as a property:
UpdateListFragmentInterface yourDelegate;
Wherever you create the Fragment within the Activity, assign the Fragment as the delegate:
// Set up and create your fragment.
(if yourFragment instanceof UpdateListFragmentInterface) {
yourDelegate = yourFragment;
}
Then in the Fragment, implement the interface:
public class YourListFragment extends android.support.v4.app.ListFragment implements UpdateListFragmentInterface {
// Constructors and other methods for the ListFragment up here.
// Override the interface method
public void updateList() {
//Update the list here.
}
}
Then finally, from within your Activity, when you need to update the list, simply call the delegate:
yourDelegate.updateList();
EDIT:
Sorry, I think you actually need to create a public method in your activity that is something like:
public void setUpdateListFragmentDelegate(UpdateListFragmentDelegate delegate) {
this.delegate = delegate
}
Then in the on attach of the List Fragment you will assign it there:
if (context instance of YourActivity) {
((YourActivity)context.setUpdateListFragmentDelegate(this);
}
I have a Fragment FR1 that contains several Nested Fragments; FRa, FRb, FRc. These Nested Fragments are changed by pressing Buttons on FR1's layout. Each of the Nested Fragments have several input fields within them; which include things like EditTexts, NumberPickers, and Spinners. When my user goes through and fills in all the values for the Nested Fragments, FR1 (the parent fragment) has a submit button.
How can I then, retrieve my values from my Nested Fragments and bring them into FR1.
All Views are declared and programmatically handled within each Nested Fragment.
The parent Fragment, FR1 handles the transaction of the Nested Fragments.
I hope this question is clear enough and I am not sure if code is necessary to post but if someone feels otherwise I can do so.
EDIT 1:
Here is how I add my Nested Fragments:
tempRangeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getChildFragmentManager().beginTransaction()
.add(R.id.settings_fragment_tertiary_nest, tempFrag)
.commit();
}
});
scheduleButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getChildFragmentManager().beginTransaction()
.add(R.id.settings_fragment_tertiary_nest, scheduleFrag)
.commit();
}
});
alertsButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getChildFragmentManager().beginTransaction()
.add(R.id.settings_fragment_tertiary_nest, alertsFrag)
.commit();
}
});
submitProfile.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
constructNewProfile();
}
});
where my constructNewProfile() method needs the values from my Nested Fragments.
public Fragment tempFrag = fragment_profile_settings_temperature
.newInstance();
public Fragment scheduleFrag= fragment_profile_settings_schedules
.newInstance();
public Fragment alertsFrag = fragment_profile_settings_alerts
.newInstance();
The above refers to the fields of the parent fragment; and how they are initially instantiated.
The best way is use an interface:
Declare an interface in the nest fragment
// Container Activity or Fragment must implement this interface
public interface OnPlayerSelectionSetListener
{
public void onPlayerSelectionSet(List<Player> players_ist);
}
Attach the interface to parent fragment
// In the child fragment.
public void onAttachToParentFragment(Fragment fragment)
{
try
{
mOnPlayerSelectionSetListener = (OnPlayerSelectionSetListener)fragment;
}
catch (ClassCastException e)
{
throw new ClassCastException(
fragment.toString() + " must implement OnPlayerSelectionSetListener");
}
}
#Override
public void onCreate(Bundle savedInstanceState)
{
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
onAttachToParentFragment(getParentFragment());
// ...
}
Call the listener on button click.
// In the child fragment.
#Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.tv_submit:
if (mOnPlayerSelectionSetListener != null)
{
mOnPlayerSelectionSetListener.onPlayerSelectionSet(selectedPlayers);
}
break;
}
}
Have your parent fragment implement the interface.
public class Fragment_Parent extends Fragment implements Nested_Fragment.OnPlayerSelectionSetListener
{
// ...
#Override
public void onPlayerSelectionSet(final List<Player> players_list)
{
FragmentManager fragmentManager = getChildFragmentManager();
SomeOtherNestFrag someOtherNestFrag = (SomeOtherNestFrag)fragmentManager.findFragmentByTag("Some fragment tag");
//Tag of your fragment which you should use when you add
if(someOtherNestFrag != null)
{
// your some other frag need to provide some data back based on views.
SomeData somedata = someOtherNestFrag.getSomeData();
// it can be a string, or int, or some custom java object.
}
}
}
Add Tag when you do fragment transaction so you can look it up afterward to call its method. FragmentTransaction
This is the proper way to handle communication between fragment and nest fragment, it's almost the same for activity and fragment.
http://developer.android.com/guide/components/fragments.html#EventCallbacks
There is actually another official way, it's using activity result, but this one is good enough and common.
Instead of using interface, you can call the child fragment through below:
( (YourFragmentName) getParentFragment() ).yourMethodName();
The best way to pass data between fragments is using Interface. Here's what you need to do:
In you nested fragment:
public interface OnDataPass {
public void OnDataPass(int i);
}
OnDataPass dataPasser;
#Override
public void onAttach(Activity a) {
super.onAttach(a);
dataPasser = (OnDataPass) a;
}
public void passData(int i) {
dataPasser.OnDataPass(i);
}
In your parent fragment:
public class Fragment_Parent extends Fragment implements OnDataPass {
...
#Override
public void OnDataPass(int i) {
this.input = i;
}
btnOk.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Fragment fragment = getSupportFragmentManager().findFragmentByTag("0");
((Fragment_Fr1) fragment).passData();
}
}
}
You can use share data between fragments.
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, item -> {
// Update the UI.
});
}
}
More Info ViewModel Architecture
You can use getChildFragmentManager() and find nested fragments, get them and run some methods to retrieve input values
Check for instanceOf before getting parent fragment which is better:
if (getParentFragment() instanceof ParentFragmentName) {
getParentFragment().Your_parent_fragment_method();
}
Passing data between fragments can be done with FragmentManager. Starting with Fragment 1.3.0-alpha04, we can use setFragmentResultListener() and setFragmentResult() API to share data between fragments.
Official Documentation
Too late to ans bt i can suggest create EditText object in child fragment
EditText tx;
in Oncreateview Initialize it. then create another class for bridge like
public class bridge{
public static EditText text = null;
}
Now in parent fragment get its refrence.
EditText childedtx = bridge.text;
now on click method get value
onclick(view v){
childedtx.getText().tostring();
}
Tested in my project and its work like charm.
I am using sliding menu/drawer pattern in my app. So the main activity has a leftView which is a ListFragment named topicsFragment() which loads set of topic items. When an item/topic is clicked it replaces the fragment on main view by calling the FeedsFragment(tag). FeedsFragment uses arraylist adapter to load the feeds which has various clickable items in each list item. I want to fetch another instance on the feedsFragment(tag) when an item is clicked within a list item.
holder.contextView= (TextView) newsView.findViewById(R.id.arcHeader);
if (item.hasArc()) {
holder.contextView.setVisibility(View.VISIBLE);
String arc;
try {
arc=item.getarc();
holder.contextView.setText(arc);
holder.contextView.setOnClickListener(new View.OnClickListener() {
//currently it loads a class
#Override
public void onClick(View v) {
Intent i = new Intent(context, SomeClass.class);
i.putExtra("tag", arc);
context.startActivity(i);
}
});
} catch (JSONException e) {
e.printStackTrace();
}
} else {
holder.contextView.setVisibility(View.GONE);
}
Currently it loads a new class. I want to define a fragment and then pass to main activity to replace with the current view but I cant use getSupportFragmentManager() inside an adapter class but only in a fragment or fragment activity. What should be an alternative to sweeping in a fragment from an adapter?
What I did was create this method in my main activity and just called it from other classes to change the fragment:
public void switchContent(Fragment fragment) {
mContent = fragment;
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, fragment).commit();
slidemenu.showContent();
}
Solved it by using the context passed in the list adapter:
#Override
public void onClick(View v) {
Fragment newFragment = new ListFragmentClass(tag);
if (newFragment != null)
switchFragment(newFragment);
}
private void switchFragment(Fragment newFragment) {
if (context == null)
return;
if (context instanceof MainActivity) {
MainActivity feeds = (MainActivity) context;
feeds.switchContent(newFragment);
}
}
Here switchContent is method defined in your main activity for switching/replacing fragment as given in answer by Justin V.
Pass getFragmentManager() as a parameter in constructor of your adapter and use that.
Use an Interface to connect your side drawer ListFragment to the main activity. For example:
public class LeftDrawer extends ListFragment{
private DrawerCallback mCallback;
public interface DrawerCallback{
public void onListClick(String tag);
}
public void setCallback(DrawerCallback callback){
mCallback = callback;
}
}
As Fragments should have an empty constructor, use a public method within your Fragment to set the callback before completing the FragmentTransaction adding it to your drawer. At this point all that is left is notifying your Fragment that a click has occurred. What you should do is actually catch the click in your ListFragment directly rather than adding on onClickListener to every view in your adapter.
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
/*
* Get item at the clicked position from your adapter and
* get its string tag before triggering interface
*/
mCallback.onListClick(tag);
}
Use the onListItemClick method to do this. You will get the list position that was clicked and can easily then get that item from your adapter and get its tag value to pass back to your host activity.