I have a parent fragment, within this. Upon a button click, a child dialog fragment is getting created.
Now I would like to know how to call parent fragment function from child dialog fragment.
Here is the sample code :
/**SampleFragment.java**/
public class SampleFragment extends Fragment {
// Instantiate view & add event handlers
public void onButtonClick(....) {
// Create a dialog framgent
}
public void refreshView() {
}
}
/**SampleDialogFragment.java**/
public class SampleDialogFragment extends DialogFragment {
// Instantiate view for dialog
public void onButtonClick(...) {
// Call parent fragment method, i.e call refreshView() of SampleFragment
}
}
In a Fragment:
SampleDialogFragment dialogFragment = new SampleDialogFragment();
dialogFragment.show(getChildFragmentManager());
In a DialogFragment:
((SampleFragment) getParentFragment()).refreshView();
After calling this method, you can access public methods of a parent fragment.
In say your parent fragment, SettingsFragment for example. Note the setTargetFragment()
public void onButtonClick(....)
{
PrefLanguageDialogFragment prefLang = PrefLanguageDialogFragment.newInstance();
prefLang.setTargetFragment(SettingsFragment.this, 1337);
prefLang.show(getFragmentManager(), "dialog");
}
In our dialog, note the getTargetFragment()
SettingsFragment frag = (SettingsFragment)getTargetFragment();
if(frag != null){
frag.refreshSomething();
}
when you want add SampleFragment to your activity set it a tag, e.g "SampleFragment".
then
public void onButtonClick(...){
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
SampleFragment parent = (SampleFragment)fm.findFragmentByTag("SampleFragment");
parent.refreshview();
}
have not test it but it may help:-)
The best way is to go for interface, declare an interface in nested fragment -
public interface checkingClickListener
{
public void checkingClickListener(String data);
}
then attach this interface to parent fragment -
public void onAttachFragment(Fragment fragment)
{
try
{
clickListener = (checkingClickListener) fragment;
} catch (ClassCastException e)
{
throw new ClassCastException(fragment.toString() + " must implement checkingClickListener");
}
}
#Override
public void onCreate(Bundle savedInstanceState)
{
Log.i(TAG, "onCreate");
super.onCreate(savedInstanceState);
this.mContext = getActivity().getApplicationContext();
onAttachFragment(getParentFragment());
....
}
you need to call this listener on some button click -
#Override
public void onClick(View v)
{
switch (v.getId())
{
case R.id.tv_submit:
if (clickListener != null)
{
clickListener.checkingClickListener("sending data");
}
break;
}
}
Implement this interface in parent fragment -
public class Fragment_Parent extends Fragment implements Nested_Fragment.checkingClickListener
{
....
#Override
public void checkingClickListener(final List<Player> players_list)
{
FragmentManager fragmentManager = getChildFragmentManager();
SomeOtherNestFrag someOtherNestFrag = (SomeOtherNestFrag) fragmentManager.findFragmentByTag([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.
}
}
}
Hope this helps you.
Related
I have the following setup:
I have an Activity that launches a FragmentA.
FragmentA contains a recyclerView and an adapter.
I have an interfaceA in the adapter which is implemented in FragmentA so that I get notified which position was clicked.
I have a second interfaceB that I created in the FragmentA, which is implemented in the Activity that launched FragmentA in step 1.
Finally, I'm launching FragmentB from the Activity based on data I get from interfaceB.
Everything is working fine, however the flow is tedious, and demands a lot of boilerplate code.
THE GOAL is to have the activity launch fragmentB that contains data from a single clicked item from the recyclerView within FragmentA.
Question: Can it be achieved differently?
Code Below:
Activity launches FragmentA:
Fragment fragment = fragmentManager.findFragmentByTag(FragmentA.class.getName());
if (fragment == null) {
fragment = Fragment.instantiate(this, FragmentA.class.getName());
}
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction
.replace(R.id.fragmentLayout, fragment, FragmentA.class.getName())
.addToBackStack(FragmentA.class.getName())
.commit();
Inside FragmentA we have recyclerView, and interfaceA implemented in the adapter:
Adapter Class:
public class AdapterA extends RecyclerView.Adapter< AdapterA.ViewHolderA> {
//instances
private Context context;
private List<Data> dataList;
private OnItemClickListener onItemListClickListener;
//Constructor
public AdapterA (Context context, List<Data> dataList, OnItemClickListener onItemListClickListener {
this.context = context;
this.dataList = dataList;
this.onItemListClickListener = onItemListClickListener;
}
onCreateViewHolder....
onBindViewHolder....
getItemCount...
class ViewHolderA RecyclerView.ViewHolder {
//instances..
//Constructor...
}
}
interface class interfaceA:
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
interface class interfaceB:
public interface SingleItemEventListener {
void onSingleItemClicked(int position);
}
FragmentA class:
//Instances
private AdapterA adapter;
private RecyclerView recyclerView;
private onSingleItemClicked singleItemEventListener;
onAttach...
onCreateView...
#Override
public void onStart() {
super.onStart();
//Setting adapter
onSetAdapter();
}
private void onSetAdapter() {
List<Data> dataList;
dataList = getData();
adapter = new AdapterA(context, dataList, new OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
singleItemEventListener.onSingleItemClicked(position);
}
});
In the Activity, we are implementing onSingleItemClicked callback to receive the event and launch FragmentB with data received from the interface callback:
ActivityA implements SingleItemEventListener {
#Override
public void onSingleItemClicked(int position) {
Data data = getData(position);
if (data != null) {
Bundle bundle = new Bundle();
bundle.putParcelable("single_data_key", data);
Fragment fragmentB = fragmentManager.findFragmentByTag(FragmentB.class.getName());
if (fragmentB == null && bundle != null) {
fragmentB = Fragment.instantiate(this, FragmentB.class.getName(), bundle);
}
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction
.replace(R.id.FragmentLayout, fragmentB, FragmentB.class.getName())
.addToBackStack(FragmentB.class.getName())
.commit();
}
}
}
Add ViewModel to your activity and use it to communicate between all you components, activity and both fragments.
You can get access to the activity's ViewModel from your fragment
MyViewModel model = ViewModelProviders.of(getActivity()).get(MyViewModel.class);
Use LiveData for communication, post an action to it from your fragment and listen to it in your activity to start another fragment.
You can do the same task using only single interface rather than two or you can share view models between fragments and activity.
Method 1 : Using single interface
Define Interface as
public interface FragmentCallbackListener {
void onItemClick(View view, int position);
}
Let your Activity implement it :
ActivityA extend Activity implements FragmentCallbackListener {
#Override
public void onItemClick(View view, int position) {
....
}
}
Fragments which are attached to activity implementing FragmentCallbackListener should override onAttach() of fragment as:
public class FragmentA extends Fragment {
private FragmentCallbackListener mListener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the listener. If not, it throws an exception
try {
mListener = (FragmentCallbackListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement FragmentCallbackListener");
}
}
}
Pass same listener instance to Adapter :
adapter = new AdapterA(context, dataList, mListener);
Method 2 : Using shared view model
Define view model as :
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Data> selected = new MutableLiveData<Data>();
public void select(Data data) {
selected.setValue(data);
}
public LiveData<Data> getSelected() {
return selected;
}
}
Inside FragmentA
public class FragmentA extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
...
adapter = new AdapterA(context, datalist, model);
}
}
Inside adapter's view onClickListener :
view.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
model.select(view.getTag()); //or use getData(position) to return selected data
}
});
Inside ActivityA
ActivityA extend Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(this).get(SharedViewModel.class);
model.getSelected().observe(this, data -> {
// launch the new fragment B with selected data
});
}
}
For more details, read https://developer.android.com/training/basics/fragments/communicating
Without an external library, have you considered using a broadcast?
It still requires boilerplate, but depending on your opinion can be a bit cleaner (the activity wont need to to be an instance of any interface, you don't need any 'god object' models)
In FragmentA's adapter, when an item is clicked you send a broadcast with an explicit action and the arguments as extras. In the activity you register (and unregister) a broadcast receiver (no need to alter manifest). Finally the receiver starts FragmentB as usual.
Fragment A / Adapter
Intent item = new Intent(MY_CONSTANT);
item.putExtra(MY_EXTRA_CONSTANT, position);
getActivity().sentBroadcast(intent);
Activity
private final BroadcastReceiver myReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
int position = intent.getIntExtra(MY_EXTRA_CONSTANT, -1);
//TODO: Move your FragmentB transaction here
}
};
void onPause() {
unregisterReceiver(myReceiver);
}
void onResume() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(MY_CONSTANT);
registerReceiver(myReceiver, intentFilter);
}
You can achieve it without using Interface, BroadcastReceiver and ViewModel.
Check below code:
ActivityA.java
public class ActivityA extends AppCompatActivity {
ActivityTestBinding mBinding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_test);
pushFragment(new FragmentA());
}
/**
* #param fragment pass fragment you want to replace
*/
public void pushFragment(Fragment fragment) {
String backStateName = fragment.getClass().getName();
FragmentManager manager = getSupportFragmentManager();
boolean fragmentPopped = manager.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped && manager.findFragmentByTag(backStateName) == null) {
// if fragment not in back stack, create it.
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.container, fragment, backStateName);
ft.addToBackStack(backStateName);
ft.commit();
}
}
#Override
public void onBackPressed() {
try {
if (getSupportFragmentManager().getBackStackEntryCount() <= 1) {
finish();
} else {
getSupportFragmentManager().popBackStack();
}
} catch (Exception e) {
super.onBackPressed();
}
}
}
Here pushFragment method is generalized for all fragment.
And in onBackPressed() it will check BackStackEntryCount. If there is 1 or less count that means only last fragment is in backstack and finish(); will be called and exit application. If there is >1 count in BackStackEntryCount then it will pop last fragment.
In your AdapterA.java:
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
if (context instanceof ActivityA) {
FragmentB mFragmentB = new FragmentB();
Bundle bundle = new Bundle();
bundle.putParcelable("single_data_key", dataList.get(getAdapterPosition()));
mFragmentB.setArguments(bundle);
((ActivityA) context).pushFragment(mFragmentB);
}
}
});
Here you already passing context of ActivityA. So you can check that if (context instanceof ActivityA) then you can call pushFragment method of ActivityA by using Type Casting.
So this is less boilerplate code you can integrate in your Android Project.
I want to add a interface in my fragment and my bottom sheet and when bottom sheet done an action(like select a button) it call in my background fragment but when i call interface it returns null and never call when my action done!
this is my code to fill interface , but the condition never called:
if(responseListener != null){
responseListener.onData( 200,message);
}
my code:
public class FilterBottomSheet extends BottomSheetDialogFragment implements View.OnClickListener {
private BottomSheetBehavior.BottomSheetCallback mBottomSheetBehaviorCallback = new BottomSheetBehavior.BottomSheetCallback() {
#Override public void onStateChanged(#NonNull View bottomSheet,int newState){
if(newState==BottomSheetBehavior.STATE_HIDDEN) {
dismiss();
}
}
}
#Override public void onSlide(#NonNull View bottomSheet,float slideOffset){}};
#SuppressLint("RestrictedApi")
#Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.bottom_sheet_filter, null);
dialog.setContentView(contentView);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) ((View) contentView.getParent())
.getLayoutParams();
CoordinatorLayout.Behavior behavior = params.getBehavior();
if (behavior != null && behavior instanceof BottomSheetBehavior) {
((BottomSheetBehavior) behavior).setBottomSheetCallback(mBottomSheetBehaviorCallback);
}
}
#Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btnDoAction:
fillActionInterface("TEST");
dismiss();
break;
}
}
public OnResponseListener responseListener;
private void fillActionInterface(String message) {
if (responseListener != null) {
responseListener.onData(200, message);
}
}
}
So I can't call interface in my another fragment. (because its never calls)
You should initialize your interface instance in onAttach method of a BottomSheet:
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
responseListener = (ResponseListener) getParentFragment();
} catch(Exception e) {
//handle exception
}
}
Please be aware that if you want to show BottomSheet dialog from fragment and get a callback in the fragment you need to do it with a getChildFragmentManager() instead of getFragmentManager() call.
If you show it with getFragmentManager() you will get cast exception in onAttach method.
Take a look at this link to see difference between ChildFragmentManager and FragmentManager.
I'm new to android and I'm trying to get a hang of creating and using Fragments.
I have a fragment that shows a simple list of multiple dates to choose from and implements an onClickListener. The idea is once a user chooses a date, the fragments sends the date back to the MainActivity which then runs a query in database and sends the database response to another fragment.
I'm stuck on the point of sending the date back to MainActivity, elegantly. I can't find much info. I found this:
Activity activity = getActivity();
if(activity instanceof MyActivity){
MyActivity myactivity = (MyActivity) activity;
myactivity.myMethod();
}
I'm very new to this but this seems hacky to me. Is this the right way or is there another way?
Any input is appreciated
I prefer the interface based approach because is very clean. You can declare a nested interface in your Fragment or an external one:
interface OnMyStuffListener {
void myMethod();
}
Make the Activity to implement that interface:
public class MainActivity extends Activity implements OnMyStuffListener {
#Override
public void myMethod() {
// Do whatever you want.
}
}
The Fragment will be attached to the Activity so you can check the instance of the Context and cast it to the Activity:
public class MyFragment extends Fragment implements View.OnClickListener {
private OnMyStuffListener mListener;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnMyStuffListener) {
mListener = (OnMyStuffListener) context;
} else {
throw new IllegalArgumentException("The context " + context.getClass().getName() +
"must implement " + OnMyStuffListener.class.getName());
}
}
#Override
public void onDetach() {
super.onDetach();
// Release it avoiding memory leak.
mListener = null;
}
#Override
public void onClick(View v) {
mListener.myMethod();
}
}
YES this is absolutely right. You can use this, if you are not sure that your Fragment is attached to Activity
You can also achieve this by using Interface, using an EventBus like LocalBroadcastManager, or starting a new Activity with an Intent and some form of flag passed into its extras Bundle or something else.
Here is an example about using Interface:
1. Add function sendDataToActivity() into the interface (EventListener).
//EventListener.java
public interface EventListener {
public void sendDataToActivity(String data);
}
2. Implement this functions in your MainActivity.
// MainActivity.java
public class MainActivity extends AppCompatActivity implements EventListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void sendDataToActivity(String data) {
Log.i("MainActivity", "sendDataToActivity: " + data);
}
}
3. Create the listener in MyFragment and attach it to the Activity.
4. Finally, call function using listener.sendDataToActivity("Hello World!").
// MyFragment.java
public class MyFragment extends Fragment {
private EventListener listener;
#Override
public void onAttach(Activity activity)
{
super.onAttach(activity);
if(activity instanceof EventListener) {
listener = (EventListener)activity;
} else {
// Throw an error!
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my, container, false);
// Send data
listener.sendDataToActivity("Hello World!");
return view;
}
#Override
public void onDetach() {
super.onDetach();
listener = null;
}
}
Hope this will help~
I am reading about how to interact between UI and background thread here.
This article has following note:
The AsyncTask does not handle configuration changes automatically,
i.e. if the activity is recreated. The programmer has to handle that
in his coding. A common solution to this is to declare the AsyncTask
in a retained headless fragment.
I dont understand what is retained headless fragment.
For example, in this way I can add fragment:
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.add(R.id.frame, new MyFragment());
transaction.commit();
And in fragment I can execute AsyncTask like this:
MyTask myTask = new MyTask();
String[] args = {"Hello"};
myTask.execute(args);
Is this called "to declare the AsyncTask in a retained headless fragment"?
Headless fragment is nothing but a fragment which does not have a view. In onCreate() of the fragment lifeCycle, use setRetainInstance(true);. This will not destroy the fragment even if the activity recreates. So if an AsyncTask is running in fragment, on recreation of the activity, you wont lose the AsyncTask.
In onCreate of the activity, you have to add the fragment with a tag. Before adding, check if the fragment exist using getFragmentManager().findFragmentByTag(TAG), if the fragment is null then create a new instance of the fragment and add it.
In Fragment there will not be any view inflated, so no need to override onCreateView().
An example of headlessFragment :
public class HeadlessProgressFragment extends Fragment {
private ProgressListener mProgressListener;
private AsyncTask<Void, Integer, Void> mProgressTask;
public interface ProgressListener {
void updateProgress(int progress);
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
public void setProgressListener(Context context) {
mProgressListener = (ProgressListener) context;
}
public void startProgress(final int size) {
if (mProgressTask == null || mProgressTask.getStatus() != AsyncTask.Status.RUNNING || mProgressTask.getStatus() == AsyncTask.Status.FINISHED) {
mProgressTask = new AsyncTask<Void, Integer, Void>() {
#Override
protected Void doInBackground(Void... params) {
for (int index = 0; index < size; index++) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
publishProgress(index + 1);
}
}
return null;
}
#Override
protected void onProgressUpdate(Integer... values) {
super.onProgressUpdate(values);
if (mProgressListener != null) {
mProgressListener.updateProgress(values[0]);
}
}
};
mProgressTask.execute();
}
}
}
In Activity Something like this :
public class MainActivity extends FragmentActivity implements HeadlessProgressFragment.ProgressListener {
private static final String TAG = "progress_fragment";
private ProgressBar mProgressBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dummy_view);
mHeadlessProgressFragment = (HeadlessProgressFragment) getSupportFragmentManager().findFragmentByTag(TAG);
if (mHeadlessProgressFragment == null) {
mHeadlessProgressFragment = new HeadlessProgressFragment();
getSupportFragmentManager().beginTransaction().add(mHeadlessProgressFragment,TAG).commit();
}
mHeadlessProgressFragment.setProgressListener(this);
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
final Button startFillBtn = (Button) findViewById(R.id.btn_start_filling);
startFillBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mHeadlessProgressFragment.startProgress(100);
}
});
}
#Override
public void updateProgress(int progress) {
mProgressBar.setProgress(progress);
}
}
As i simplified the complexity in my case by Just update your UI (if you have to) by checking the calling fragment or activity is present or not. Start the asynctask by assigning the weakreference of calling entity.
I am using Android Studio which implements all the functions of a fragment class. I am getting following runtime exception:
must implement OnFragmentInteractionListener
Here is my code and I have implemented the OnFragmentInteractionListener in my main activity.
MAIN ACTIVITY:
public class MainActivity extends FragmentActivity implements BlankFragment.OnFragmentInteractionListener {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (findViewById(R.id.fragment_container) != null) {
BlankFragment fragment = new BlankFragment();
android.support.v4.app.FragmentManager manager = getSupportFragmentManager();
android.support.v4.app.FragmentTransaction ft = manager.beginTransaction();
ft.add(R.id.fragment_container, fragment).commit();
}
#Override
public void onFragmentInteraction(Uri uri) {}
}
BLANK FRAGMENT:
public class BlankFragment extends android.support.v4.app.Fragment {
private OnFragmentInteractionListener mListener;
public interface OnFragmentInteractionListener {
public void onFragmentInteraction(Uri uri);
}
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
}
The remaining methods of fragment class and main activity are also implemented by default. I have also changed LinearLayout with FrameLayout in the main_activity.xmlfile and also assignedandroid:id` to it which is referenced fine. What is wrong with my code?
To interact with your BlankFragment object, I would use the following method recommended in the Android Support Docs - I believe this is what you are trying to achieve, and it will be suitable as your BlankFragment object is hosted by MainActivity:
public class MainActivity extends FragmentActivity implements BlankFragment.OnFragmentInteractionListener {
private BlankFragment fragment = null;
private android.support.v4.app.FragmentManager manager = null;
private android.support.v4.app.FragmentTransaction ft;
public void onFragmentInteraction(Uri uri){
Log.i("Tag", "onFragmentInteraction called");
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (manager == null) manager = getSupportFragmentManager();
if(manager.findFragmentById(R.id.fragment_container) == null) {
//If a fragment is not already loaded into your container, then add one...
fragment = new BlankFragment();
ft= manager.beginTransaction();
ft.add(R.id.fragment_container,fragment).commit();
}
}
In order to communicate with your fragment, you would do the following:
if(fragment == null) {
fragment = (BlankFragment) manager.findFragmentById(R.id.fragment_container);
}
You can then call any methods associated with fragment
If it's the other way round (communication from fragment to activity), then you would need to do the following in BlankFragment to form a link with the parent Activity:
//Class variable...
OnFragmentInteractionListener mCallback;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
You may have forgotten about this last step, which could explain your error. You would then use:
mCallback.onFragmentInteraction(uri);
To communicate with MainActivity
This worked for me, I have just shared it here you never know it will help out some one else.
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment).commit();
Just one line of code.