I have activity and two fragments which opened in this activity (for example FragmentFirst, FragmentSecond). In my activity I register BroadcastReceiver. And when i receive any event (inside onReceive) I need to display the received text in the TextView of second fragment.
I could register the BroadcastReceiver in the second fragment, however, the event may come at the moment when my second fragment opens and then I do not have time to register the BroadcastReceiver and I lose the event.
So my code inside the onReceive of activity looks like this:
#Override
public void onReceive(Context context, Intent intent) {
String receivedMessage = intent.getStringExtra(MY_MESSAGE);
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.container);
if (currentFragment instanceof FragmentSecond) {
((FragmentSecond) currentFragment).initMessage(receivedMessage);
}
}
And now I have a question, can there be such a situation that the current fragment is FragmentSecond, but the view of this fragment has not yet been created. In this case, I can get a NPE when calling a initMessage of my FragmentSecond that sets the receivedMessage for the TextView.
If this is really possible, then I will have to add some kind of checks inside the initMessage:
public void initMessage(String receivedMessage) {
if (isViewCreated) { // flag to detect when view was created
tvMessage.setText(receivedMessage);
savedMessage = "";
} else {
savedMessage = receivedMessage; // save message to display it when view will be created
}
}
In practice, I was not able to reproduce such a situation, but it seems to me that this is possible. Please tell me if this is possible?
P.S. I get the feeling that the onReceive controls the life cycle and is called exactly when the fragment view is created. Because I tried to send a broadcast until the moment when the FragmentSecond view was created, but until the onViewCreated of my FragmentSecond was called, onReceive was not called. However, I may be wrong
You can store the message in the Activity and use it the fragment WHEN fragment is ready.
Activity
String message = null;
#Override
public void onReceive(Context context, Intent intent) {
message = intent.getStringExtra(MY_MESSAGE);
}
You can "touch" the Activity in onAttach(context) method of the fragment.
Fragment
private String message = null;
void onAttach(Context: context) {
super.onAttach(context)
if (context instanceof MainActivity) {
MainActivity mainActivity = (MainActivity) context;
message = mainActivity.message;
}
}
That is for grabbing the latest value.
Since you are operating in Java is it a bit more verbose but for updates you can use Observer pattern(create listener interface which the fragments will implement and register themselves to the activity).
interface MessageObserver {
void onMessageChange(String message);
}
Use WeakReference for the Activity so it doesn't leak the context.
Fragment
private WeakReference<MainActivity> mWeakRefActivity;
#Override
public void onAttach(#NonNull Context context) {
super.onAttach(context);
if (context instanceof MainActivity) {
MainActivity mainActivity = (MainActivity) context;
mWeakRefActivity = new WeakReference<MainActivity>(mainActivity);
mainActivity.addObserver(this);
}
}
#Override
public void onDetach() {
// Clean up after yourself
mWeakRefActivity.get().removeObserver(this);
super.onDetach();
}
Activity
ArrayList<MessageObserver> mObservers = new ArrayList<MessageObserver>();
void addObserver(MessageObserver observer) {
mObservers.add(observer);
}
void removeObserver(MessageObserver observer) {
mObservers.remove(observer);
}
public void onReceive(Context context, Intent intent) {
message = intent.getStringExtra(MY_MESSAGE);
for (MessageObserver observer : mObservers) {
observer.onMessageChange(message);
}
}
Related
I have a fragment (FragmentSearchResults) that contains results retrieved from a database, in which there is a button "filters". When the user taps on such a button, a class (FiltersDialog) extending a BottomSheetDialogFragment is instantiated, so that the user can set his filters. When the user closes the FiltersDialog activity, the values are passed from FiltersDialog to FragmentSearchResults.
public class FragmentSearchResults extends Fragment implements FiltersDialog.FilterListener {
/* code */
ImageButton btnFilter = myFragment.findViewById(R.id.btn_filters);
btnFilter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showFilters();
}
});
}
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
mContext = context;
}
#Override
public void onFiltersSet(Map filters) {
// apply filters selected by user
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
}
public class FiltersDialog extends BottomSheetDialogFragment {
private FilterListener mListener;
private Map<String, Object> mFilters;
public FiltersDialog() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_filters_dialog, container, false);
TextView txtSave = v.findViewById(R.id.txt_save_filters);
mTxtSave.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListener.onFiltersSet(mFilters);
}
});
return v;
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
if (context instanceof FilterListener) {
mListener = (FilterListener) context;
}
else {
// Here's the error, as the activity Home.java containing FragmentSearchResults
// does not implement FilterListener, FragmentSearchResults does
throw new RuntimeException(context.toString() + " must implement FilterListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}
The problem is that FilterListener needs to be implemented in FragmentSearchResults, but I am passing the activity Home context.
How can I implement FilterListener in the fragment?
Why don't you create method inside your FiltersDialog, like
public void setFiltersListener(FiltersDialog.FilterListener listener) {
mListener = listener;
}
and simply call it after you instantiate the dialog.
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setFiltersListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
Then you can use the listener inside dialog. something like this
if (mListener != null) {
mListener.onFiltersSet(mFilters);
}
How can I setup listener to the dialog?
parameter of onAttach in Fragment is FragmentHost(Activity). thus, it can't typecast to FilterListener.
I suggest a simple way to implement FilterListener setter in FragmentDialog as below code.
... in FiltersDialog
public void setListener(FilterListener listener) {
mListener = listener;
}
...
... in FragmentSearchResults
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
...
//When FragmentSearchResults recreated, FiltersDialog must also need to be recreated.
A better approach will be to use LiveData, ViewModel in this case. Use Shared ViewModel Approach, An Activity Level ViewModel can be accessed via all the fragments lying in its environment.
Make an Activity Level ViewModel
Define a LiveData in ViewModel
When your "FragmentSearchResults" opens for the first time, start
observing it.
When You open "FiltersDialog" screen and click save button, Then post
to LiveData changes in the filter (You have activity context here,
You can fetch ActivityViewModel here, get LiveData from it, post
changes to this LiveData)
Now As "FragmentSearchResults" is already observing changes in the
LiveData, You will get callback here, make changes accordingly. This way your code will be completely decoupled. You will be escaped from
hustles of Interfaces.
I have an activity and I create a fragment when this activity runs. How do I get the data from the fragment to the activity that creates the fragment?
The part where I create an intent in my activity:
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
RoutePlansFragment routePlansFragment = RoutePlansFragment.newInstance();
FragmentTransactionUtil.addFragment(getSupportFragmentManager(), getFragmentContainerId(), routePlansFragment, routePlansFragment.getFragmentTag());
}
This is my fragment:
public static RoutePlansFragment newInstance() {
RoutePlansFragment routePlansFragment = new RoutePlansFragment();
return routePlansFragment;
}
How can I send data from this fragment into activity?
you can use a callback
in your fragment you have to create a listener
public interface DataListener{
void sendData(String data);
}
create a reference
private DataListener mDataListener;
and in onAttach you have to caste your activity as DataListener
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof DataListener) {
mDataListener = (DataListener) context;
}
}
your activiy must implements DataListener
and u can send data from fragment by calling sendData method
i m using fragments in my application. i created one Parent Class called BaseFragment and all other fragment extends this Basefrgment below is the snippet of this Basefragment
BaseFragment.java
public class BaseFragment extends Fragment {
public MainActivity activity;
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (activity == null && context instanceof MainActivity) {
activity = (MainActivity) context;
}
}
}
public void replaceFragment(Fragment fragment, FragmentDetail last) {
fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
boolean push = true;
if (Validator.isNotNull(last)) {
push = false;
}
/*if(Validator.isNull(last)){
transaction.setCustomAnimations(R.anim.enter_from_left, R.anim.exit_to_right);
}else{
transaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left);
}*/
transaction.add(R.id.frame_container, fragment, fragment.getClass().getName());
if (Validator.isNull(last) && preferences.getFragmentStack().size() > 0) {
last = preferences.getFragmentStack().lastElement();
}
if (Validator.isNotNull(last)) {
Fragment f = fragmentManager.findFragmentByTag(last.className);
if (Validator.isNotNull(f)) {
f.onPause();
transaction.remove(f);
}
}
last = new FragmentDetail(fragment.getClass().getName(), getTitle().toString(), preferences.isBack());
if (preferences.isBack() || preferences.getFragmentStack().size() == 0) {
if (push) {
preferences.getFragmentStack().push(last);
}
} else {
while (preferences.getFragmentStack().size() > 1) {
preferences.getFragmentStack().pop();
}
if (!preferences.getFragmentStack().lastElement().className.equals(last.className)) {
preferences.getFragmentStack().push(last);
}
}
transaction.commitAllowingStateLoss();
changeNavigationIcon();
// HWUtil.showToast(this, fragmentManager.getBackStackEntryCount() + "");
}
and in all other fragment i m using activity as a context,my question is whether it is bad way to access context in this way or whether it creates memory leak.or any other approach for accessing context..any help is appriciated.
I think the way you are storing the context is really optimal as with that you will be able to use it within each of your sub fragment instances. Because MainActivity is an instance variable in your fragment, it will be garbage collected when your fragment gets destroyed. And if I'm not mistaken about Activity-Fragment lifecycle, when your activity gets rotated, new fragments will be created and the older fragment instances will get destroyed. So, we're good there too. However, you need to be careful with your context variable declaration:
public MainActivity activity;
This makes it accessible from anywhere. Any class can call something like context = fragIns.activity and save it there. This will be really bad for you because now it holds a reference to that context variable. Now, when your fragment is no longer needed it will not be garbage collected because some other class is holding a reference to one of its variable. You'd find yourself in "memory leak town".
Make sure you hold this variable dearly and its reference is not passed to other classes. Since, its in a super class, you may define it as:
protected MainActivity activity;
This should do the job.
the best way is to use getActivity() function inside fragment to access context,
because it will return the instance of the activity on which fragment is attached.
using getActivity() is easy and quick way to get parent activity's context but problem comes when that fragment is detached.
So using it like this inside fragment , imo, will suffice the need of doing it better way....
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mContext=activity;
}
To avoid memory problems it's recommended that whenever you use onAttach(Context context) you should use onDetach() as well:
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (activity == null && context instanceof MainActivity) {
activity = (MainActivity) context;
}
}
#Override
public void onDetach() {
this.activity = null;
super.onDetach();
}
My way:
public class AppManager extends Application {
private static AppManager mApp;
#Override
public void onCreate() {
super.onCreate();
mApp = this;
}
public static Context getContext() {
return mApp.getApplicationContext();
}
}
so, anyone wants to get a Context, just using the AppManager.getContext(), except starting a Activity. It is very simple.
In your way, if the activity restarted, the fragment will automatically create again. In you dont' handle the action of Activty restarted, it is posssible that the Activity have two same Fragment, and one who automatically creating didn't call OnAttch(), will causing NullPointerException.
My solution:
public abstract class BaseTabActivity extends BaseActivity {
#CallSuper
protected void initTabs(boolean isRestarted) {
if (isRestarted) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
if (manager.getFragments() == null)
return;
Stream.of(manager.getFragments())
.forEach((fragment) -> {
if (fragment != null)
transaction.remove(fragment);
});
transaction.commit();
manager.executePendingTransactions();
}
}
public FragmentTransaction getSlideAnimTransaction() {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setCustomAnimations(R.anim.slide_from_right, R.anim.slide_out_left);
return transaction;
}
}
I have a Fragment that needs to communicate more than one Action back to it's Activity. For example,
When a button is clicked, it needs to communicate the onClick back to the Activity.
2.When a user's login and password match, a boolean value is sent to the Activity notifying it to start an Intent.
My first question is, is this common where a Fragment needs to relay more that one type of Action back to the Activity? And secondly, how is this solved? Is the following a good way to do it...
I created a custom class, which extends Fragment and included the two interfaces that I need (One to pass the onClick back to the Activity and One to pass a boolean value):
public class CustomInterfaceFragment extends Fragment {
public OnClickedListener listener;
public LogInInterface loggedInListener;
static interface OnClickedListener{
public void buttonClicked(View v);
}
static interface LogInInterface{
public void userLoggedIn(boolean loggedIn);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
this.listener = (OnClickedListener)activity;
this.loggedInListener = (LogInInterface)activity;
}}
I then extended this custom class in my Fragment and used the appropriate methods where needed. This is the onClick method in the Fragment...
#Override
public void onClick(View v) {
switch (v.getId()){
case R.id.register_button:{
listener.buttonClicked(v);//***Pass onClick Back to Activity
break;
}
case R.id.fragment_login_loginButton:{
ParseUser.logInInBackground(userName.getText().toString(), password.getText().toString(), new LogInCallback() {
#Override
public void done(ParseUser user, ParseException e) {
if (user!=null){
boolean verified = user.getBoolean("emailVerified");
if(!verified){
Toast.makeText(getActivity(),"Please Verify",Toast.LENGTH_LONG).show();
progressDialog.dismiss();
ParseUser.logOut();
}else{
progressDialog.dismiss();
loggedInListener.userLoggedIn(true);//***Pass boolean Back to Activity
}
}else {
Toast.makeText(getActivity(),e.getMessage(),Toast.LENGTH_LONG).show();
progressDialog.dismiss();
}
}
});
}
break;
}
}
Finally I implemented the custom fragment class and its interfaces in my Activity in order to retrieve the data.
Is this a reasonable way to solve this problem or am I missing something? The application seems to work fine. I just want to know what the best programming practice would be. Thank you.
all i can say is you can bring down this two interfaces to one like this below
public interface fragmentInteractions{
public void OnClickedListener(View v);
public void userLoggedIn(boolean loggedIn);
....
....
}
and i don't think the interface here needs to be static
Elaborating on Avinash Joshi's answer :
public interface CustomListener {
void onButtonClicked();
void onLoginResult( boolean isUserLoggedIn ); // You can pass User object via this method in case its required to do some operations
}
public class MainActivity extends Activity implements CustomListener {
#Override
public void onCreate( Bundle savedInstance ) {
// Initialize UI elements
// Initialize Fragment
}
#Override
public void onButtonClicked() {
//Action to be performed on button click
}
#Override
public void onLoginResult( boolean isUserLoggedIn ) {
if( isUserLoggedIn ) {
//take user to dashboard or any other screen
//Usually with the help of SupportFragmentManager
}
else {
//Take user to signup screen with an optional toast message
//In case parameters like User name and password need not be entered by user again, you can access them as function parameters and pass them to signupFragment via bundle
}
}
}
public class LoginFragment extends Fragment {
CustomListener mCustomListener;
#Override
public void onAttach( Context context ) {
super.onAttach( Context context );
try {
mCustomListner = (CustomListener) context;
} catch ( ClassCastException e {
Log.e(TAG, "Activity must implement CustomListener")
}
}
//Rest of Fragment initialization code here
}
Here's a complete example :
http://www.truiton.com/2015/12/android-activity-fragment-communication/
There are lots of discussions about data exchange between activities and fragments, but I am still struggeling with a design flaw.
I have an activity that needs to receive some data from an service. When this data has changed, the service sends a local broadcast and the activity needs to get the latest dataset.
public class ActivityChannelConfig extends Activity implements FragmentChannelList.FragmentChanneListInferface, FragmentCustomChannelList.FragmentCustomChannelListInterface {
#Override
protected void onCreate(Bundle savedInstanceState) {
// Bind to Bluetooth Service
bindService(new Intent(this, ServiceBluetoothConnection.class),
mServiceConnection, Context.BIND_AUTO_CREATE);
}
#Override
public void onServiceConnected(ComponentName className,
IBinder service) {
ActivityChannelConfig activity = mActivity.get();
if(activity == null)
return;
ServiceBluetoothConnection.BTConnServiceBinder binder = (ServiceBluetoothConnection.BTConnServiceBinder) service;
mService = binder.getService();
mIsBound = true;
// Put current list into channel list fragment
FragmentCustomChannelList customChannelList = (FragmentCustomChannelList)activity.getFragmentManager().findFragmentByTag(activity.TAG_FRAGMENT_CUSTOMIZE);
if(customChannelList != null) {
customChannelList.setChannelList(activity.mServiceConnection.getService().getmDBMeasurement().getSortedMeasChannels(DBMeasurement.MEAS_CHANNEL_FLAG.MEAS_CHANNEL_ENABLED));
}
FragmentChannelList channelList = (FragmentChannelList)activity.getFragmentManager().findFragmentByTag(activity.TAG_FRAGMENT_LIST);
if(channelList != null) {
channelList.setChannelList(activity.mServiceConnection.getService().getmDBMeasurement().getSortedMeasChannels(DBMeasurement.MEAS_CHANNEL_FLAG.MEAS_CHANNEL_ENABLED));
}
}
}
#Override
public void onReceive(Context context, Intent intent) {
final ActivityChannelConfig activity = mActivity.get();
// Bail out if the ActivityMarwisApp is gone.
if (activity == null)
return;
// Channel list has changed
if(intent.getAction().equals(DBMeasurement.INTENT_DB_CHANNEL_LIST_CHANGED)) {
// Put current list into channel list fragment
FragmentCustomChannelList customChannelList = (FragmentCustomChannelList)activity.getFragmentManager().findFragmentByTag(activity.TAG_FRAGMENT_CUSTOMIZE);
if(customChannelList != null) {
customChannelList.setChannelList(activity.mServiceConnection.getService().getmDBMeasurement().getSortedMeasChannels(DBMeasurement.MEAS_CHANNEL_FLAG.MEAS_CHANNEL_ENABLED));
}
FragmentChannelList channelList = (FragmentChannelList)activity.getFragmentManager().findFragmentByTag(activity.TAG_FRAGMENT_LIST);
if(channelList != null) {
channelList.setChannelList(activity.mServiceConnection.getService().getmDBMeasurement().getSortedMeasChannels(DBMeasurement.MEAS_CHANNEL_FLAG.MEAS_CHANNEL_ENABLED));
}
}
}
}
Now my problem is that I don't know, how to pass this data to the fragments inside my activity initial and dynamically. There always is the problem, that the fragment might ne be created when the service is connected or the broadcast is received.
Do I need to implement a two way interaction between the fragment and the activity?
activity.updateList -> fragment
fragment.getCurrentList -> activity
Or should I register a new braodcast receiver in each fragment, which might lead to lots of redundant code?
I know that I could also pass the data using the onCreate Bundle. But I am not a fan of making all that stuff parcable, which I also belive is very slow and inefficient.
You can use local broadcast manager to communicate between multiple fragments and intents
//Send Broadcast
private void sendLocationBroadcast(Intent intent, Context context, String Message){
intent.putExtra("Message", Message);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
//In this snippet you point out your fragment or activity by given sample
if(Origin == Enum_Fragments.Task.getInteger())
{
intent = new Intent("Task");
sendLocationBroadcast(intent, context, "Task");
}
// What I have done here is made a enum for fragments and trigger service with related Identity (which was assigned by me to each fragment) after condition identifies the Argument (Origin) it will send data to desired framgment
Here is enum class for ref
public enum Enum_Fragments{
Task(1), Frag Notes(2), Default(0);
Enum_Fragments(int s) {
INTEGER_FOR_MODULE = s;
}
private int INTEGER_FOR_MODULE;
public int getInteger() {
return INTEGER_FOR_MODULE;
}
public void setInteger(int iNTEGER_FOR_MODULE) {
INTEGER_FOR_MODULE = iNTEGER_FOR_MODULE;
}
public static Enum_Fragments value(String i) {
try {
return valueOf(i);
} catch (Exception ex) {
return Default;
}
}
this will send your broadcast for provided intent with some particular data after job done you may receive it at the activity as follows
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
String Message = intent.getStringExtra("Message");
//Todo
Log.d("Local BroadCast", "Recieved " + Message);
}
};