I have a ViewPager inside a FragmentActivity. For each page I have a Fragment with an EditText.
I want to prevent the user leaving the screen if the EditText value has changed, showing a warning Dialog asking if he/she really wants to leave the screen.
For that I need to check the EditText value when the back Button is pressed.
I have only found how to do it but using the onBackPressed() method in the container FragmentActivity.
The problem is that I don't know how to access the EditText inside the Fragment from the parent FragmentActivity.
Is it not possible just to do it in the Fragment? I've tried the method onStop and onDetach but they are called after leaving the Fragment, so I cannot stop it.
Any ideas?
Try this in your parent Activity:
#Override
public void onBackPressed() {
super.onBackPressed();
YourFragment fragment = (YourFragment) getSupportFragmentManager().getFragments().get(viewPager.getCurrentItem());
}
Then you have access to your Fragment so you can add a parameter in your fragment textHasChanged (for example) and update it when the text changes.
You may create interface which has method onBackPressed(your fragment should implement this interface), then override method onBackPressed in your activity, then find your fragment by tag or id -> cast to this interface and call this method.
So you need BackPressed() event in fragment for you problem to check if editext is values is changed or not?
So you have create you own OnBackpressed() interface.
Create a interface OnBackPressedListener
public interface OnBackPressedListener {
public void doBack();
}
Create a new class name BaseBackPressedListener
public class BaseBackPressedListener implements OnBackPressedListener {
private final FragmentActivity activity;
public BaseBackPressedListener(FragmentActivity activity) {
this.activity = activity;
}
#Override
public void doBack() {
activity.getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
//activity.getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
Create a instance of your newly created class BaseBackPressedListener in your top base Activity where fragment is added
protected OnBackPressedListener onBackPressedListener;
Create a method in your top base activity to set newly created instance of new class above
public void setOnBackPressedListener(OnBackPressedListener onBackPressedListener) {
this.onBackPressedListener = onBackPressedListener;
}
you will have overide onBackPressed() method in you top base activity modify it like this
#Override
public void onBackPressed() {
if (onBackPressedListener != null)
onBackPressedListener.doBack();
else
super.onBackPressed();
}
And lastly you have add this in you fragment onCreateView()
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Activity activity = getActivity();
((BaseActivity)activity).setOnBackPressedListener(new BaseBackPressedListener(activity));
//your other stuff here
return view;
}
Now you can check on back pressed in your top base activity weather editext value change our not by modifying this methods
#Override
public void doBack() {
if(!IsEditTextChanged)
activity.getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
//activity.getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
IsEditTextChanged is global variable set its values on textchanged
mEditText.addTextChangedListener(new TextWatcher() {
#Override
public void afterTextChanged(Editable s) {}
#Override
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start,
int before, int count) {
if(s.length() != 0)
IsEditTextChanged = true;
// your desired logic here?
}
});
Fragment BackPressed thread
You could create a Boolean value in the FragmentActivity and keep it updated with the EditText updated. In this way you can check the String value instead the EditText (In fact the fragment could not be loaded in the Pager).
For example:
1) create an interface in order to declare the input protocol
public interface InputInterface {
void setEditTextNotEmpty(Boolean notEmpty);
}
2) implement this interface in your FragmentActivity
public class MainActivity implements InputInterface {
private Boolean editTextNotEmpty;
...
#Override
void setEditTextNotEmpty(Boolean changed){
editTextNotEmpty=changed;
}
#Override
public void onBackPressed() {
... your custom code....
}
3) You somehow notify the Fragment when it's visualized as for in this post: How to determine when Fragment becomes visible in ViewPager
When the fragment is visualized, you update the activity boolean if necessary.
4) keep your MainActivity updated when you update the edit text:
yourEditText.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
if (getActivity instanceof InputInterface){
Boolean notEmpty = !TextUtils.isEmpty(s.toString);
((InputInterface)getActivity).setEditTextNotEmpty(notEmpty);
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
I hope it helped.
Related
I've got simple application consisting of 2 screens.
On the first I've got EditText view and button.
When user entry text and press button, main screen changes and user can see typed value together with switch button that's there for returning to previous destination.
The problem's when user haven't typed anything and press button. I've got exception that says, "string is empty".
I've tried using textChangedListener but that isn't working.
First screen consist of ViewModel and MutableLiveData object for storing typed value.
Here's the code:
Main screen:
public class MainFragment extends Fragment {
private MainViewModel mViewModel;
public static MainFragment newInstance() {
return new MainFragment();
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.main_fragment, container, false);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = new ViewModelProvider(this).get(MainViewModel.class);
// TODO: Use the ViewModel
EditText editText = getView().findViewById(R.id.valueEntry);
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
return;
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
mViewModel.setValue(Float.valueOf(editText.getText().toString()));
}
#Override
public void afterTextChanged(Editable editable) {
return;
}
});
Button button = getView().findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
MainFragmentDirections.MoveToSecond action = MainFragmentDirections.moveToSecond( );
action.setVal(mViewModel.getValue());
Navigation.findNavController(view).navigate(action);
}
});
}
ViewModel instance:
public class MainViewModel extends ViewModel {
private MutableLiveData<Float> value = new MutableLiveData<>();
public void setValue(Float c){value.setValue(c);}
public Float getValue()
{
return value.getValue();
}
use this code when the user pressed the button
if (!entered_sting.equals(""))
{
// show text to other works
}
use this if in first of button press event
A cleaner option would be to check for an empty string and not for equals("")
if (!entered_sting.trim().isEmpty())
{
// show text to other works
}
I ran into some strange UI issues while trying to display a custom content AlertDialog. The dialog asks the user to enter a name and it doesn't allow him to move forward without doing so. It is also the first thing that the user sees when the activity starts.
Sometimes, right after the application gets restarted - let's say I press the home button when the dialog is opened and then I reopen the app, the AlertDialog is being displayed as it should be but the parent activity's layout is not being loaded correctly. It actually keeps the layout from the previous Activity that the user was seeing. Even stranger, this layout is almost always displayed backwards. You can probably see that better in here. Behind the dialog it should be a blank white layout but instead there's a reverted "snapshot" of the launcher activity from the Settings app.
As the official documentation suggests I am wrapping the AlertDialog in a DialogFragment.
public class NicknamePickerDialog extends DialogFragment {
public static final String TAG = NicknamePickerDialog.class.getSimpleName();
public interface NicknameDialogListener {
void onNicknamePicked(String nickname);
void onPickerCanceled();
}
private NicknameDialogListener mListener;
private EditText mNicknameEditText;
private Button mPositiveButton;
public void setNicknameDialogListener(NicknameDialogListener listener) {
mListener = listener;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Set the title
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.pick_nickname);
// Inflate the custom content
View dialogView = getActivity().getLayoutInflater().inflate(R.layout.nickname_dialog_layout, null);
builder.setView(dialogView);
mNicknameEditText = (EditText) dialogView.findViewById(R.id.nickname);
builder.setPositiveButton(R.string.great, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if (mListener != null) {
mListener.onNicknamePicked(mNicknameEditText.getText().toString());
}
}
});
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
if (mListener != null) {
mListener.onPickerCanceled();
}
}
});
final AlertDialog dialog = builder.create();
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
#Override
public void onShow(DialogInterface dialogInterface) {
mPositiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
mPositiveButton.setEnabled(false);
}
});
mNicknameEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) { }
#Override
public void afterTextChanged(Editable s) {
mPositiveButton.setEnabled(s.length() != 0);
}
});
return dialog;
}
}
This is the Activity code
public class ChatActivity extends Activity implements NicknamePickerDialog.NicknameDialogListener {
private String mNickname;
private TextView mWelcomeTextView;
private NicknamePickerDialog mDialog;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chat_activity_layout);
mWelcomeTextView = (TextView) findViewById(R.id.welcome);
mDialog = new NicknamePickerDialog();
mDialog.setNicknameDialogListener(this);
}
private void showNicknamePickerDialog() {
mDialog.show(getFragmentManager(), NicknamePickerDialog.TAG);
}
#Override
public void onNicknamePicked(String nickname) {
mNickname = nickname;
mWelcomeTextView.setText("Welcome " + nickname + "!");
}
#Override
public void onPickerCanceled() {
if (mNickname == null) {
finish();
}
}
#Override
protected void onResume() {
super.onResume();
if (mNickname == null) {
showNicknamePickerDialog();
};
}
#Override
protected void onPause() {
super.onPause();
mDialog.dismiss();
}
}
At first I suspected that it probably happens because I am calling the DialogFragment's show method inside the activity's onCreate() callback (as it might be too soon), but postponing it to as late as onResume() does not solve the problem. This issue also occurs on orientation changes, leaving the background behind the dialog black. I am sure I am doing something wrong but I really can't find out what that is.
I am seriously not getting that what you are trying to do. but one thing you have done the wrong is that.
Do overide method OnCreateView() in class NicknamePickerDialog and do the below
// Inflate the custom content
View dialogView = getActivity().getLayoutInflater().inflate(R.layout.nickname_dialog_layout, null);
builder.setView(dialogView);
mNicknameEditText = (EditText) dialogView.findViewById(R.id.nickname);
mNicknameEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) { }
#Override
public void afterTextChanged(Editable s) {
mPositiveButton.setEnabled(s.length() != 0);
}
});
return dialogView;
also your alert dialog will not work . better create buttons and title you can in onCreateDialog().
dialog.setTitle(R.string.pick_nickname);
Hope this will work.
I have MainActivity with FrameLayout in witch I load fragments.
Lets say that in MainActivity there is a Button next, that loads the next fragment
public void btnNextClick(View view) {
Fragment currentFragment;
currentFragment = new F2Fragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.replace(R.id.fragment_switch, currentFragment);
fragmentTransaction.commit();
}
This part would load next fragment to screen (in this case f2Fragment)
Inside first fragment (f1Fragment) i have EditText field and I need to change the fragment only if there is some input in EditText
I try to use SharedPreferences and
pref=activity.getSharedPreferences("default", 0);
edt = pref.edit();
public void onPause() {
super.onPause();
edt.putString("edtText",edtText.getText().toString()).apply();
}
Later in MainActivity i try to get that and check length
public void btnNextClick(View view) {
pref = this.getSharedPreferences("default",0);
String edtText = pref.getString("edtText","");
if(edtText.length()>0){
Fragment currentFragment;
currentFragment = new F2Fragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.replace(R.id.fragment_switch,currentFragment);
fragmentTransaction.commit();
}
}
This works only first time, if i stay edit blank and press next nothing happen, if i insert some text nothing again, if i start app and insert text, press next it work.
And what if i have two edits that i need check?
Thank you.
public class ActivityActivityTablet extends Activity implements FragmentA.OnEditBoxTextLoadNewFragment{
.
.
.
//other Activity methods and your code
.
.
#Override
public void OnEditBoxTextLoadNewFragment(String textFromFragA) {
//load new fragment//if else will work if you need more than loading the fragment
}
}
Fragment should be like this:
public class ActivityFragment extends Fragment {
private OnEditBoxTextLoadNewFragment mListener;
//other fragment code of yours
public interface OnEditBoxTextLoadNewFragment{
public void OnEditBoxTextLoadNewFragment(String edit text);
}
EditText text= (EditText) findViewById(R.id.text1);
text1.addTextChangedListener(new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
if (!s.toString().equalsIgnoreCase("")) {
mListener.OnEditBoxTextLoadNewFragment(s);
}
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void afterTextChanged(Editable s) {
}
});
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnEditBoxTextLoadNewFragment) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEditBoxTextLoadNewFragment");
}
}
}
I have a listfragment in an activity, a dialogfragment will open on pressing on a row for the further options, now I rotate the device and choose a option from dialogfragment, its throws an IllegalStateException .... Fragment is not attached.
choiceDialog
.setOnClickListDialogClickListener(new StandardListDialogFragment.OnClickListener<String>() {
#Override
public void onClick(DialogInterface dialog, int which,
String value) {
Intent intent = new Intent(this.getActivity(),
DutyEditor.class);
startActivityForResult(intent, 0);
dialog.dismiss();
}
});
This happens because the Fragment is destroyed and re-created with a FragmentManager whenever orientation changes.
You should stick to these rules when working with Fragments
Avoid setters when using Fragments.
Never hold a field reference to a Fragment in an Activity.
You can hold a field reference to an Activity on a Fragment after onAttach() and before onDetach(), but I find it better to call getActivity() and check for null every time I need it for short operations.
The best option is to call startActivityForResult() in the DialogFragment itself.
But whenever you really need to deliver the DialogFragment click events to the Activity, use an Interface. For example.
public final class SomeDialogFragment extends DialogFragment {
/**
* Callbacks of {#link SomeDialogFragment}
*/
public interface SomeDialogFragmentCallbacks {
/**
* Called when user pressed some button
*/
void onSomeButtonClick();
}
#Override
public void onAttach(final Activity activity) {
super.onAttach(activity);
// Make sure the Activity can receive callbacks
if (!(activity instanceof SomeDialogFragmentCallbacks)) {
throw new RuntimeException("Should be attached only to SomeDialogFragmentCallbacks");
}
}
// now whenever a button is clicked
#Override
public void onClick(DialogInterface dialog, int which) {
final SomeDialogFragmentCallbacks callbacks = (SomeDialogFragmentCallbacks) getActivity();
if (callbacks != null) {
callbacks.onSomeButtonClick();
}
}
}
And all Activities that use this DialogFragment should implelent the callbacks method.
public final class SomeActivity extends Activity implements SomeDialogFragmentCallbacks {
#Override
public void onSomeButtonClick() {
// Handle some DialogFragment button click here
}
}
I have an activity with 20 EditTexts on it. I want to display a message if anything has been entered. Instead of attaching 20 listeners to each field, is there a way to know if input has been supplied(data changed)?
Here is an example of a base class that takes care of notifying subclasses if any of their EditText widgets is being used(if any exist). Depending on what you want to do when the user inputs something you may need to change my example to suit your particular case.
Base activity from which you'll inherit:
public abstract class BaseTextWatcherActivity extends Activity {
private TextWatcher mInputObserver = new TextWatcher() {
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
#Override
public void afterTextChanged(Editable s) {
onInputChange(s.toString());
}
};
private boolean mOptimize = false;
#Override
public void setContentView(int layoutResID) {
View content = getLayoutInflater().inflate(layoutResID, null);
initInputsObservers((ViewGroup) content);
mOptimize = true;
super.setContentView(content);
}
#Override
public void setContentView(View view) {
if (view instanceof EditText) {
((EditText) view).addTextChangedListener(mInputObserver);
} else if (view instanceof ViewGroup && !mOptimize) {
initInputsObservers((ViewGroup) view);
mOptimize = false;
}
super.setContentView(view);
}
/**
* Implement this method to allow your activities to get notified when one
* of their {#link EditText} widgets receives input from the user.
*
* #param s the string that was entered by the user in one of the EditTexts.
*/
public abstract void onInputChange(String s);
/**
* This will recursively go through the entire content view and add a
* {#link TextWatcher} to every EditText widget found.
*
* #param root
* the content view
*/
private void initInputsObservers(ViewGroup root) {
final int count = root.getChildCount();
for (int i = 0; i < count; i++) {
final View child = root.getChildAt(i);
if (child instanceof EditText) {
((EditText) child).addTextChangedListener(mInputObserver);
} else if (child instanceof ViewGroup) {
initInputsObservers((ViewGroup) child);
}
}
}
}
Then all you have to do in your activities is implement the onInputchange callback to let you know that the user entered something in one of the EditTexts from the current layout. You may want to look at the key event callbacks from the activity class for something simpler, but if I'm not mistaken those events are consumed by the TextView/EditText widgets.
Have your TextViews all call the addTextChangedListener() giving them your Activity as an argument (this). Have your Activity implement TextWatcher and override afterTextChanged().
Whenever one of the TextView changes, afterTextChanged() will be called.
Here is an idea of what your code should look like
public class YouActivity extends Activity implements TextWatcher{
#Override
public void onCreate(Bundle savedInstanceState)
{
[...]
textView1.addTextChangedListener(this);
textView2.addTextChangedListener(this);
[...]
}
#Override
public void afterTextChanged (Editable s)
{
//do something
}
}