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/
Related
Im trying to listen or pass data from an BotomSheetDialogFragment into Fragment to change something on the Fragment (Just like a picker).
I've tried with getTargetFragment to instantiate the listener but getting a compiler error Found: 'MyFragment', required: 'android.support.v4.app.Fragment' less..
Any ideas or i'm takin the wrong approach?
public class MyBottomSheetDialogFragment extends BottomSheetDialogFragment implements View.OnClickListener {
ReportType reportType;
public interface OnChooseReasonListener {
void onChooseReason(ReportType reportType);
}
OnChooseReasonListener listener;
#Override
public void setupDialog(Dialog dialog, int style) {
super.setupDialog(dialog, style);
View contentView = View.inflate(getContext(), R.layout.picker_bottom_sheet_, null);
dialog.setContentView(contentView);
CoordinatorLayout.LayoutParams layoutParams =
(CoordinatorLayout.LayoutParams) ((View) contentView.getParent()).getLayoutParams();
CoordinatorLayout.Behavior behavior = layoutParams.getBehavior();
//get null here!!!:
listener = (OnChooseReasonListener) getParentFragment();// or with getTargetFragment();
}
#Override
public void onClick(View view) {
switch (view.getId()){
case R.id.cool_button:
this.reportType = ReportType.ME;
//trying to execute the lisstener on null
listener.onChooseReason(this.reportType);
dismiss();
break;
}
}}
And the fragment:
public class MyFragment extends Fragment
implements View.OnClickListener,
MyBottomSheetDialogFragment.OnChooseReasonListener {
//....code here
public void showPicker() {
//getting and compiler error Wrong 1st argument type.
// picker. setTargetFragment(MyFragment.this , 300);
picker.show(fm, picker.getTag());
}
#Override
public void onChooseReason(ReportType reportType) {
//not getting here
Log(TAG, "You choose something" + reportType.getValue());
}
}
Besides that it's not working, that code smells a little since you're coupling MyBottomSheetDialogFragment with the object that created it.
The correct approach would be to have a method void setOnChooseReasonListener(OnChooseReasonListener listener) on MyBottomSheetDialogFragment and call it when you create the instance.
myBottomSheetDialogFragment.setOnChooseReasonListener(this);
You can approach this by using the interface
First
Create an interface class
interface CustomInterfaceClass {
public void callbackMethod(String date);
}
Second,
Initialize the interface class in Activity or fragment
As I am using in the fragments class
//interface for callback
private CustomInterface callback;
Third, Make sure you have initialized the callback interface object within the onCreateView or OnCreate method.
//if you facing an error while initializing such as this keyword
not assigned to the callback method that means you didn't implement
the interface fragmentAclass.
callback=this;
Fourth,
Don't forget to implement the override method within the FragmentAClass
#Override
public void callbackMethod(String date) {
Toast.makeText(getContext(), "Yes"+date, Toast.LENGTH_SHORT).show();
}
Fifth,
Now move to BottomSheetDialogFragment or FragmentBclass
Add callback method constructor such as this
private CustomInterface callback;
public Disconnect_fragment( CustomInterface callback) {
this.callback=callback;
}
public Disconnect_fragment( ) {
}
Lastly Now you can pass the value by using this method and will receive in the FragmentAclass
callback.callbackMethod("your passing value");
I want my app to make a notification after the user toggles a switch in settings. The settings page is static so I can't use showNotification() here. Is there any way to build a notification like that??
public static class MainPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_main);
final SwitchPreference notiswitch = (SwitchPreference) findPreference(getString(R.string.settings_notification_key));
notiswitch.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
if(!((Boolean) newValue)) { //default false
Toast.makeText(getActivity(), "OFF",
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getActivity(), "ON",
Toast.LENGTH_SHORT).show();
//TODO make notification
showNotification();//error here
}
return true;
}
});
}
}
public void showNotification() {
Looks like you made MainPreferenceFragment as static inner class inside an activity class which contains the showNotification() method and serves as host for the fragment.
My suggestion is to use a classic approach of activity-fragment communication - through the casting fragment's hosting activity to the required interface and call appropriate method from it.
For example:
Create a new interface in separate file:
public interface NotificationView{
void showNotification();
}
Then make the activity that responsible for showing fragment implementing the interface, and override the showNotification method.
After that add to the MainPreferenceFragment class a private field NotificationView callback; and initialize it in this way:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (activity instanceOf NotificationView){
callback = (NotificationView) activity;
}
}
And now you are able to call callback.showNotification(); from where you need inside your fragment. But don't forget to check callback on null before using in case you use the fragment with other activity.
Hope it will help!
I have implemented the Android Tutorial from the Android Training Site and everything works so far.
Inside of a Fragment, I added an EditText field, where the user can enter a simple text string.
What I want to achieve is: If the user entered something, and then swipes to left or right, the input should be "copied" the new fragment as well.
As in the training, I have got a ScreenSlidePageFragment.java class and a ScreenSlideActivity.java class.
I've implemented an Interface in the ScreenSlidePageFragment, as mentioned here:
Communicator mCallback;
public interface Communicator {
void sendData(String inpString);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mCallback = (Communicator) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + "must Implement OnEditChangedListener");
}
}
But at the end, I just managed to receive it on the ScreenSlideActivity. How do I update a specified fragment?
In ScreenSlideActivity I tried to send it back to the new fragment.
#Override
public void sendData(String inpString) {
WHATFRAGMENT.changeData(inpString);
}
But how do I access another one (WHATFRAGMENT), which are created in the ScreenSlidePagerAdapter:
#Override
public Fragment getItem(int position) {
return NewReservationSlidePageFragment.create(position);
}
Do I need to create a whole Fragment and overwrite the new position? Or is it even necessary to work around the Activity? Is it possible to use FragmentTransaction somehow?
What would be the simplest solution for that?
You can achieve with your interface and public void setUserVisibleHint(boolean isVisibleToUser)
Add one more method in your interface like this,
public interface Communicator {
void sendData(String inpString);
String getData();
}
And in you activity create one String variable Globally to store the value.
And assign the data coming form sendData in that
#Override
public void sendData(String inpString) {
this.value = inpString;
//WHATFRAGMENT.changeData(inpString);
}
and implement getData too in your Activity
#Override
public void getData() {
return this.value;
}
Add setUserVisibleHint in your fragment
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
if(isVisibleToUser) {
mCallback.sendData("[string from your edit text]");
} else {
String value = mCallback.getData();
}
}
Now you can see the data which you edited in one fragment can show in other visible fragment.
I am new to Android MVP Architecture. As far as I have researched the Presenter should be kept free from any android things like for example: Don't use getActivity or Context in the Presenters. I have written the following code where a BasePresenter is the parent class of all the Presenter classes that I will be using.The BaseView interface is the parent interface of all View classes and BaseActivity class is the parent class of all Activity classes. I have more than one activity and it is required to show Toast messages in all of my activity. So I have written the following code as follows. I am not very sure whether using the getactivity from the presenter class is a good practice or not. If it is not then can anyone suggest any better way to do it?
BasePresenter class
public class BasePresenter<V extends BaseView> {
private V mView;
private Context mContext;
public void attachView(V view) {
mView = view;
mContext=mView.getActivity();
}
public void showToast(String msg) {
Toast.makeText(getContext(), msg, Toast.LENGTH_SHORT).show();
}
private Context getContext() {
return mContext;
}
public void detachView() {
mView = null;
}
}
BaseView class
public interface BaseView {
AppCompatActivity getActivity();
}
BaseActivity class
public class BaseActivity extends AppCompatActivity {
public AppCompatActivity getActivity() {
return this;
}
}
MainActivity class
public class MainActivity extends BaseActivity implements MainView {
MainPresenter basePresenter;
#Override
protected void onStart() {
super.onStart();
basePresenter = new MainPresenter();
basePresenter.attachView(this);
}
// some more codes here
switch (item.getItemId()) {
case R.id.about:
basePresenter.showToast("About is Clicked");
break;
case R.id.cart:
basePresenter.showToast("Cart is Clicked");
break;
case R.id.favs:
basePresenter.showToast("Favs is Clicked");
break;
case R.id.home:
basePresenter.showToast("Home is Clicked");
break;
}
}
It is not a good idea. You Presenter (base or otherwise) should not know about Context, Activity, Toast or anything else Android based.
View
displays things.
handles user input and passes it to the Presenter.
Presenter
decides what to do with user input.
gathers data from the model.
tells the View what to do.
So for your example of clicking Buttons and showing Toasts you would need a setup something like:
View Interface
This is how your Presenter will talk to your View. It will be implemented by the Activity.
public interface MainView {
void showToast(String message);
}
Presenter (Base & Main)
BasePresenter has almost no tasks at all. Simply there to bind the View interface. Note the method names in the MainPresenter are ambiguous to things like 'click' to seperate them from the View implementation.
public class BasePresenter<V> {
protected V view;
public void attachView(V view) {
this.view = view;
}
}
public class MainPresenter extends BasePresenter<MainView> {
public void about() {
view.showToast("About was clicked");
}
public void cart() {
view.showToast("Cart was clicked");
}
}
Activity
The Activity implements the View interface. It's responsible for passing user events to the Presenter and actioning the Presenter commands.
public class MainActivity extends AppCompatActivity implements MainView {
private MainPresenter presenter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
presenter = new MainPresenter();
presenter.attachView(this);
Button about = findViewById(R.id.button_about);
about.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
presenter.about();
}
});
Button cart = findViewById(R.id.button_cart);
cart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
presenter.cart();
}
});
}
#Override
public void showToast(String message) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
}
In this setup the Presenter no longer knows anything about Android (has no imports from the framework at all) and you are able to write unit tests for it which can run directly on the JVM without Android dependencies.
Toast is actually visible on screen. So It should not be in presenter. It should be triggered from the View.
I am using the new KitKat Transitions API on Android. I have created two Scene objects using two layouts. I animate from Scene 1 to Scene 2 inside a Fragment. I want to automatically move back to the previous Scene when the user presses the back button.
Is there some kind of built-in backstack mechanism when using Transitions, or do I have to roll my own?
It is easy enough to call TransitionManager.go(scene1), but I really do not want to implement an onBackPressed() listener in all my fragments that have Scene animations.
I ended up rolling my own solution.
Have your Activity implement this
public interface SceneBackstackHandler {
public void addBackstackListener(BackstackListener listener);
public void removeBackstackListener(BackstackListener listener);
public void removeAllBackstackListeners();
public interface BackstackListener {
public boolean onBackPressed();
}
}
Activity
private final Object mBackstackListenerLock = new Object();
private List<BackstackListener> mBackstackListeners = new ArrayList<>();
#Override
public void onBackPressed() {
synchronized (mBackstackListenerLock) {
for (BackstackListener mBackstackListener : mBackstackListeners) {
if (mBackstackListener.onBackPressed()) {
// handled by fragment
return;
}
}
super.onBackPressed();
}
}
#Override
protected void onPause() {
super.onPause();
removeAllBackstackListeners();
}
#Override
public void addBackstackListener(BackstackListener listener) {
synchronized (mBackstackListenerLock) {
mBackstackListeners.add(listener);
}
}
#Override
public void removeBackstackListener(BackstackListener listener) {
synchronized (mBackstackListenerLock) {
mBackstackListeners.remove(listener);
}
}
#Override
public void removeAllBackstackListeners() {
synchronized (mBackstackListenerLock) {
mBackstackListeners.clear();
}
}
Child Fragment:
public class MySceneFragment extends Fragment
implements SceneBackstackHandler.BackstackListener {
private Scene mCurrentScene;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mBackstackHandler = (SceneBackstackHandler) activity;
mBackstackHandler.addBackstackListener(this);
}
#Override
public void onDetach() {
super.onDetach();
mBackstackHandler.removeBackstackListener(this);
}
#Override
public boolean onBackPressed() {
if (mCurrentScene != null && mCurrentScene.equals(mMyScene)) {
removeMyScene();
return true;
}
return false;
}
private void changeScene(Scene scene) {
TransitionManager.go(scene);
mCurrentScene = scene;
}
}
I use an Otto event bus to communicate between my Activity and Fragments. The controlling Activity maintains its own Stack of custom back events which each contain a back action Runnable, i.e. what action should be taken when the back button is pressed.
The advantage to this approach is a slightly more decoupled design and should scale with more fragments. For readability, I have defined the Otto Events inside my Fragment, here, but these could be easily moved elsewhere in your project.
Here's some sample code to give you an idea of how it's done.
Fragment(s)
The Fragment signals its intent to take hold of the next back press by posting a BackStackRequestEvent to the Otto event bus and supplying a Runnable action to be executed when the event is popped off the Activity's custom stack. When the Fragment is detached, it sends a ClearBackStackEvent to the bus to remove any of the Fragment's back actions from the activity's custom stack.
public class MyFragment extends Fragment {
private final String BACK_STACK_ID = "MY_FRAGMENT";
...
public class BackStackRequestEvent {
private Runnable action;
private String id;
public BackStackRequestEvent(Runnable action, String id) {
this.action = action;
this.id = id;
}
public void goBack() {
action.run();
}
public String getId() {
return id;
}
}
public class ClearBackStackEvent {
private String id;
public ClearBackStackEvent(String id) {
this.id = id;
}
public String getId() {
return id;
}
}
...
#Override
public void onDetach() {
super.onDetach();
// Get your Otto singleton and notify Activity that this
// Fragment's back actions are no longer needed
// The Fragment lifecycle stage in which you do this might vary
// based on your needs
EventBus.getInstance().post(new ClearBackStackEvent(BACK_STACK_ID));
}
...
public void someChangeInFragment() {
// Notify the Activity that we want to intercept the next onBackPressed
EventBus.getInstance().post(new BackStackRequestEvent(new Runnable()
{
#Override
public void run() {
// Reverse what we did
doBackAction();
}
}, BACK_STACK_ID)); // constant used later to remove items from Stack
}
}
Activity
The activity registers / unregisters its interest in the events we defined above in onStart() and onStop(). When it receives a new BackStackRequestEvent it adds it to its custom back stack. Once onBackPressed() is called, it pops the back stack and invokes the back action using BackStackRequestEvent.goBack() which in turn runs the Fragment's Runnable. If there is nothing on the Stack, the normal back behaviour is followed.
When the Fragment is detached, the Activity receives a ClearBackStackEvent and it removes all items of the supplied id from the Stack.
public class MyActivity extends Activity {
private Stack<MyFragment.BackStackRequestEvent> customBackStack = new Stack<>();
...
#Override
protected void onStart() {
super.onStart();
EventBus.getInstance().register(this);
}
#Override
protected void onStop() {
super.onStop();
EventBus.getInstance().unregister(this);
}
#Subscribe // Annotation indicating that we want to intercept this Otto event
public void backStackRequested(MyFragment.BackStackRequestEvent request) {
customBackStack.push(request);
}
#Override
public void onBackPressed() {
if (customBackStack.empty()) {
// No custom actions so default behaviour followed
super.onBackPressed();
}
else {
// Pop the custom action and call its goBack() action
MyFragment.BackStackRequestEvent back = customBackStack.pop();
back.goBack();
}
}
#Subscribe
public void clearBackStackRequested(MyFragment.ClearBackStackEvent request) {
String id = request.getId();
for (MyFragment.BackStackRequestEvent backItem : customBackStack) {
if (backItem.getId().contentEquals(id)) {
customBackStack.remove(backItem);
}
}
}
}