I have not found a clear solution anywhere on stack for this.
Here's my basic set up
public class Activity1 extends AppCompatActivity
{
private OnAttributesUpdatedListener onAttributesUpdatedListener;
public interface OnAttributesUpdatedListener
{
public void onAttributesUpdated();
}
public void setTargetFragment(Fragment fragment)
{
this.onAttributesUpdatedListener = (OnAttributesUpdatedListener) fragment;
}
private void whenFinishedSomethingCallback()
{
onAttributesUpdatedListener.onAttributesUpdated();
}
}
public class Fragment1 extends Fragment implements Activity1.OnAttributesUpdatedListener
{
button.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View view) {
if(rivalButtonClick == 0)
{
Activity1 activity1 = new Activity1();
activity1.setTargetFragment(Fragment1.this);
startActivity(new Intent(getActivity(), activity1.getClass()));
}
}
});
}
I get a null pointer exception and crashes on : onAttributesUpdatedListener.onAttributesUpdated(); because for some reason my listener never gets set properly. What's the proper way to do this?
You need to set the listener at start of the fragment onCreatView() or in onActivityCreated() only if the Desired Activity is a parent Activity of that particular fragment. Below is an example .
public class Activity1 extends AppCompatActivity {
private OnAttributesUpdatedListener onAttributesUpdatedListener;
public interface OnAttributesUpdatedListener {
public void onAttributesUpdated();
}
public void setListener(OnAttributesUpdatedListener onAttributesUpdatedListener) {
this.onAttributesUpdatedListener = onAttributesUpdatedListener;
}
private void whenFinishedSomethingCallback() {
if(onAttributesUpdatedListener!=null)
onAttributesUpdatedListener.onAttributesUpdated();
}
}
public class Fragment1 extends Fragment implements Activity1.OnAttributesUpdatedListener
{
#Override
public void onAttributesUpdated() {
// Do your stuff here
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
((Activity1)getActivity()).setListener(this);
}
}
Read about fragment Life cycle to make use of getActivity(). also remove the listener when fragment is destroyed .
Use LocalBroadcastManager for communicating between in case the Fragment exists in other Activity.
At first create an Interface like this:
public interface Listener{
void doSomething() }
Then implement this interface in your activity.
And also add
Listener listener
In your fragment
And in onAttach method in fragment use this
listener=(Listener)activity
Then call listener whenever you need .
Related
I have an Activity which has a Save details button and a Viewpager which contains 4 fragments. The Fragments contains User details form. Once the Save button is clicked I need to get the data from all fragments and save the details. How to get the data entered by the user in all 4 fragments when Save button is clicked in the activity?
I just worked on an app that had the same use case. In addition, I had to save the data on a back navigation as well. The problem was a bit more difficult that I though it should have been. The problems came from the fact that not all the fragments in the ViewPager are guaranteed to be alive. They either may not have been started yet, or destroyed when the user paged off of them.
To solve the problem, I took inspiration from this blog post about handing back-press events. I had to modify it a bit to allow for any fragments that may be running and not just one.
public abstract class BackHandledFragment extends Fragment {
protected BackHandlerInterface backHandlerInterface;
public abstract String getTagText();
public abstract boolean onBackPressed();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!(getActivity() instanceof BackHandlerInterface)) {
throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
} else {
backHandlerInterface = (BackHandlerInterface) getActivity();
}
}
#Override
public void onResume() {
super.onResume();
backHandlerInterface.addRunningFragment(this);
}
#Override
public void onPause() {
super.onPause();
backHandlerInterface.removeRunningFragment(this);
}
public interface BackHandlerInterface {
public void addRunningFragment(BackHandledFragment backHandledFragment);
public void removeRunningFragment(BackHandledFragment backHandledFragment);
}
}
The Activity implements the interface and tracks the active fragments:
public class EditActivity implements BackHandledFragment.BackHandlerInterface
{
private List<BackHandledFragment> listActiveFragments = new ArrayList<>
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Notify each active fragment that the back was pressed, this will allow
// them to save any data.
for (BackHandledFragment bf : listActiveFragments) {
bf.onBackPressed();
}
}
#Override
public void addRunningFragment(BackHandledFragment backHandledFragment) {
listActiveFragments.add(backHandledFragment);
}
#Override
public void removeRunningFragment(BackHandledFragment backHandledFragment) {
listActiveFragments.remove(backHandledFragment);
}();
}
Each fragment must extend BackHandledFragment:
public class DetailNetworkFragment extends BackHandledFragment {
#Override
public void onPause() {
super.onPause();
EventBus.getDefault().unregister(this);
saveDataFields();
}
#Override
public void onResume() {
super.onResume();
EventBus.getDefault().register(this);
}
#Override
public String getTagText() {
return TAG;
}
#Override
public boolean onBackPressed() {
saveDataFields();
return false;
}
}
The saveDataFields() is not too interesting. It just copies the data out of the UI views and saves them back to an object in the Activity.
I have this interface in my activity.
public interface LogoutUser {
void logout();
}
My fragment implements this interface, so in my fragment, I have this:
#Override
public void logout() {
// logout
}
In my activity I call
mLogoutUser.logout();
Where mLogoutUser is of the type LogoutUser interface.
My issue is the mLogoutUser object that is null. How can initialize it?
Thank you!
As I said in my comment, I resolved this issue using onAttach method in my fragment, but in this way you have to have the callback field (mLogoutUser in this case) declared in the fragment, and initialize it this way:
public class MyFragment extends ListFragment {
LogoutUser mLogoutUser;
// Container Activity must implement this interface
public interface LogoutUser {
public void logout();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mLogoutUser = (LogoutUser) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement LogoutUser");
}
}
...
}
More info in Communicating with Other Fragments.
But if your case is the field declared in the activity, you can use the onAttachFragment method from your activity to initialize your listener field this way:
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
mLogoutUser = (LogoutUser) fragment;
}
Also, you can use an event bus to make this communication between fragments and activities. An option is the Otto library, from Square.
Sample for creating callback from Fragment to Activity
public interface CallBackListener {
void onCallBack();// pass any parameter in your onCallBack which you want to return
}
CallBackFragment.class
public class CallBackFragment extends Fragment {
private CallBackListener callBackListener;
public CallBackFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_call_back, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//getActivity() is fully created in onActivityCreated and instanceOf differentiate it between different Activities
if (getActivity() instanceof CallBackListener)
callBackListener = (CallBackListener) getActivity();
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Button btn = (Button) view.findViewById(R.id.btn_click);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(callBackListener != null)
callBackListener.onCallBack();
}
});
}
}
CallbackHandlingActivity.class
public class CallbackHandlingActivity extends AppCompatActivity implements CallBackListener
{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_all_user);
}
#Override
public void onCallBack() {
Toast.makeText(mContext,"onCallback Called",Toast.LENGTH_LONG).show();
}
}
Android Fragments - Communicating with Activity
You need to get a reference to your fragment with getFragmentById() or getFragmentByTag()
getFragmentManager().findFragmentById(R.id.example_fragment);
You can use kotlinx Channel to send data or callback between fragments and activity or vice versa
In your Mainactivity:
val loginPromptChannel = Channel<LoginPromptState>()
val loginStateFlow = loginPromptChannel.receiveAsFlow()
//onCreate
lifecycleScope.launchWhenStarted {
loginStateFlow.collect() { state ->
when (state) {
is LoginPromptState.Login -> {
//smooth scroll to login fragment
binding.viewpager.setCurrentItem(2, true)
}
}
}
}
//create sealed a class
sealed class LoginPromptState {
object Login : LoginPromptState()
}
In your fragment send callback like:
lifecycleScope.launch {
val channelLogin = (activity as MainActivity).loginPromptChannel
channelLogin.send(MainActivity.LoginPromptState.Login)
}
I have been researching this for a few days and have yet to find a working solution. There is lots of information available but because of my inexperience with Android I can't get any of the suggestions to work.
I have an Activity with a stack of 3 Fragments on top of it all of which are presented using FragmentManager Transactions and added to the backstack. While the third Fragment is active, I need to intercept the onBackPressed() method and perform some extra stuff before the Fragment is destroyed.
I have tried using Callbacks and Interfaces to capture onBackPressed() at the Activity and send it to the 3rd Fragment with no luck.
What is the proper way to have a Fragment deep in the stack watch for the Activity's onBackPressed() method.
Let me know if this is not clear.
Thanks for the help.
Not compiled and tested, but this lays out the basic approach:
public interface BackButonListener {
boolean OnBackButtonPressed();
}
public interface BackButtonWatchable {
void addBackButtonListener(BackButtonListener listener);
void removeBackButtonListener(BackButtonListener listener);
}
public class MyActivity extends Activity implements BackButtonWatchable {
...
private static ArrayList<BackButtonListener> backButtonListeners
= new ArrayList<BackButtonListener>();
#Override
public void addBackButtonListener(BackButtonListener listener) {
backButtonListeners.add(listener);
}
#Override
public void removeBackButtonListener(BackButtonListener listener) {
backButtonListeners.remove(listener);
}
...
#Override
public void onBackButtonPressed()
{
boolean supressBackButton = false;
for (BackButtonListener listener: backButtonListeners)
{
if (!listener.OnBackButtonPressed()) {
suppressBackButton = true;
}
}
if (!suppressBackButton) {
super.onBackButtonPressed();
}
}
}
public class MyFragment extends Fragment implements BackButtonListerer {
#Override
public void onResume()
{
((BackButtonWatchable)getActivity()).addBackButtonListener(this);
}
#Override
public void onPause() {
((BackButtonWatchable)getActivity()).removeBackButtonListener(this);
}
}
Crete interface
public interface OnBackPressedListener {
void onBackPressed();
}
and create field in activity
private OnBackPressedListener mListener;
and your onBackPressed() should look like
if (mListener != null) {
mListener.onBackPressed();
} else { /* do your acitivty usual stuff */ }
When fragment is created you register this fragment as mListener in your activity and don't forger to set it to null in onDestroy.
This is the post that answered my question. For a Android newbie, this told me where everything needed to go.
https://stackoverflow.com/a/30865486/2640458
The Fragment that needed to see the onBackPress() method from it's activity:
public class RatingFragment extends Fragment implements ContentActivity.OnBackPressedListener {
#Override
public void doBack() {
getFragmentManager().popBackStack();
}
The very important subscription to the listener in the above Fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_rating, container, false);
((ContentActivity)getActivity()).setOnBackPressedListener(this);
}
The Activity that needs to send the onBackPress() method to the above Fragment:
public class ContentActivity extends Activity {
protected OnBackPressedListener onBackPressedListener;
public interface OnBackPressedListener {
void doBack();
}
public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
this.onBackPressedListener = onBackPressedListener;
}
#Override
public void onBackPressed() {
if (onBackPressedListener != null)
onBackPressedListener.doBack();
else
super.onBackPressed();
}
#Override
protected void onDestroy() {
onBackPressedListener = null;
super.onDestroy();
}
}
I have a main activity class, and a private inner class within the main activity. The private inner class has methods that when called will display fragments. This inner class implements an interface defined in the Fragment's class, to be used as a sort of callback. It is probably easiest to show through code.
public class MainActivity extends FragmentActivity {
//on a button clicked
EditItemManger em = new EditItemManager();
em.begin();
private class EditItemManager implements on EditItemFragment.EditedItemClickedListener{
//consructor, other stuff. no onCreate method because this inner class does not (explicity??) extend activty
public void begin(){
EditItemFragment editItemFrag = new EditItemFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(editItemFrag, EDIT_FRAG_TAG)
fragmentTransaction.commit();
}
#Override
public void onEditItemClicked() {
editFinish();
}
public void editFinish()
{
// other stuff
}
}
}
My EditItemFragment class, where the onAttach method always has a null activity parameter
public class EditItemFragment extends DialogFragment {
protected EditedItemClickedListener editedItemClickedListener;
protected ImageButton button;
public EditItemFragment(){}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
final Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.edit_name_fragment, container, false);
button = (ImageButton) view.findViewById(R.id.submit_new_item_button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
editedItemClickedListener.onEditedItemButtonClicked();
}
});
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
editedItemClickedListener= (EditedItemClickedListener) activity;
} catch (ClassCastException e) {
e.printStackTrace();
}
}
public interface OnEditNameButtonClickedListener {
public void onEditNameButtonClicked();
}
So because the parameter in onAttach() in my Fragment class is always null, it eventually causes a null pointer exception. I am wondering if it is because the fragment is called from a class that is not extending activity. The problem is that if this class extends activity, there will be an issue with trying to commit the Fragment Transaction
I guest your onAttach returns nullpointer exception. Its because your parent Activity the main activity doesnt implement your custom interface so it return null. See the code below let me know if it help you:
public class MainActivity extends FragmentActivity implements EditItemFragment.EditedItemClickedListener{
private DialogFragment editItemDialog;
//Do your code here
#Override
public void onEditItemClicked() {
editFinish();
}
public void showDialog(){
//this is for showing your custom dialog fragment
editItemDialog = EditItemFragment.newInstance();
editItemDialog.show(getSupportFragmentManager(),"editItemDialog");
}
}
this is for your EditItemFragment:
public class EditItemFragment extends DialogFragment{
//Do your code here
public static EditItemFragment newInstance(){
EditItemFragment editItemDialog = new EditItemFragment();
return editItemDialog;
}
}
I have this interface in my activity.
public interface LogoutUser {
void logout();
}
My fragment implements this interface, so in my fragment, I have this:
#Override
public void logout() {
// logout
}
In my activity I call
mLogoutUser.logout();
Where mLogoutUser is of the type LogoutUser interface.
My issue is the mLogoutUser object that is null. How can initialize it?
Thank you!
As I said in my comment, I resolved this issue using onAttach method in my fragment, but in this way you have to have the callback field (mLogoutUser in this case) declared in the fragment, and initialize it this way:
public class MyFragment extends ListFragment {
LogoutUser mLogoutUser;
// Container Activity must implement this interface
public interface LogoutUser {
public void logout();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mLogoutUser = (LogoutUser) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement LogoutUser");
}
}
...
}
More info in Communicating with Other Fragments.
But if your case is the field declared in the activity, you can use the onAttachFragment method from your activity to initialize your listener field this way:
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
mLogoutUser = (LogoutUser) fragment;
}
Also, you can use an event bus to make this communication between fragments and activities. An option is the Otto library, from Square.
Sample for creating callback from Fragment to Activity
public interface CallBackListener {
void onCallBack();// pass any parameter in your onCallBack which you want to return
}
CallBackFragment.class
public class CallBackFragment extends Fragment {
private CallBackListener callBackListener;
public CallBackFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_call_back, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//getActivity() is fully created in onActivityCreated and instanceOf differentiate it between different Activities
if (getActivity() instanceof CallBackListener)
callBackListener = (CallBackListener) getActivity();
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Button btn = (Button) view.findViewById(R.id.btn_click);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(callBackListener != null)
callBackListener.onCallBack();
}
});
}
}
CallbackHandlingActivity.class
public class CallbackHandlingActivity extends AppCompatActivity implements CallBackListener
{
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_all_user);
}
#Override
public void onCallBack() {
Toast.makeText(mContext,"onCallback Called",Toast.LENGTH_LONG).show();
}
}
Android Fragments - Communicating with Activity
You need to get a reference to your fragment with getFragmentById() or getFragmentByTag()
getFragmentManager().findFragmentById(R.id.example_fragment);
You can use kotlinx Channel to send data or callback between fragments and activity or vice versa
In your Mainactivity:
val loginPromptChannel = Channel<LoginPromptState>()
val loginStateFlow = loginPromptChannel.receiveAsFlow()
//onCreate
lifecycleScope.launchWhenStarted {
loginStateFlow.collect() { state ->
when (state) {
is LoginPromptState.Login -> {
//smooth scroll to login fragment
binding.viewpager.setCurrentItem(2, true)
}
}
}
}
//create sealed a class
sealed class LoginPromptState {
object Login : LoginPromptState()
}
In your fragment send callback like:
lifecycleScope.launch {
val channelLogin = (activity as MainActivity).loginPromptChannel
channelLogin.send(MainActivity.LoginPromptState.Login)
}