I'm having problems getting data from my dialog to the calling activity. Been following the android documents, but haven't had luck. I just want to capture a string that someone enters in an edit text box of my dialog. Here is my setup.
public class CrossoverMainActivity extends Activity implements CrossoverSaveDialog.NoticeDialogListener {
final CrossoverSaveDialog dialog_save = new CrossoverSaveDialog();
#Override
public void onDialogPositiveClick(DialogFragment dialog) {
// User touched the dialog's positive button
}
#Override
public void onDialogNegativeClick(DialogFragment dialog) {
// User touched the dialog's negative button
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
if (id == R.id.action_save) {
dialog_save.show(getFragmentManager(), "Dialog_save");
return true;
}
if (id == R.id.action_delete) {
CrossoverDeleteDialog dialog_delete = new CrossoverDeleteDialog();
//Fragment fragment = new Fragment();
dialog_delete.show(getFragmentManager(), "Dialog_delete");
return true;
}
return super.onOptionsItemSelected(item);
}
}
Then, in another .java file, I have my dialog:
public class CrossoverSaveDialog extends DialogFragment {
public interface NoticeDialogListener {
void onDialogPositiveClick(DialogFragment dialog);
void onDialogNegativeClick(DialogFragment dialog);
}
// Use this instance of the interface to deliver action events
NoticeDialogListener mListener;
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
LayoutInflater inflater = getActivity().getLayoutInflater();
View dialogSave = inflater.inflate(R.layout.crossover_save_dialog, null);
builder.setView(dialogSave);
builder.setPositiveButton(R.string.dialog_save, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mListener.onDialogPositiveClick(CrossoverSaveDialog.this);
}
})
.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
mListener.onDialogNegativeClick(CrossoverSaveDialog.this);
}
});
return builder.create();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Verify that the host activity implements the callback interface
try {
// Instantiate the NoticeDialogListener so we can send events to the host
mListener = (NoticeDialogListener) activity;
} catch (ClassCastException e) {
// The activity doesn't implement the interface, throw exception
throw new ClassCastException(activity.toString()
+ " must implement NoticeDialogListener");
}
}
}
Here are my two questions:
The problem I'm getting is that the compiler is complaining that I'm not implementing the methods of my interface... which I seem to be. Is there something I'm missing here?
I have to do this for a second dialog which is different than the one I've shown here. But, I'll be doing it in the same fashion. Do I just look in the dialog of the callback method to determine which dialog is being used? That way I can handle my logic for each dialog. Or, should I have each dialog with a seperate interface that uses it's each unique methods, and just implement each of those?
You must set your activity to be the Listener for your newly member "mListener"
So in Dialogclass you should create a getter/setter for your listener like this:
public NoticeDialogListener getListener() { return mListener; }
public void setListener (NoticeDialogListener listener) { mListener=listener; }
and in your activity you would do
CrossoverDeleteDialog dialog_delete = new CrossoverDeleteDialog();
dialog_delete.setListener(this);
dialog_delete.show(getFragmentManager(), "Dialog_delete");
I actually figured out the answer here.
#blender: Maybe I'm mis-understanding this, but I don't think I need to do that right? I mean, I'm implementing the interface already in my activity class, and I'm getting attached to that via the onAttach within my dialog class aren't I?
Anyhow, here was the blunder. I was importing the android.support.v4.app.DialogFragment. Then, in my main activity, I was importing the android.app.DialogFragment. That was causing my two method callbacks in my main activity to not actually get registered by the compiler it seemed. I used Android Studio so when you auto import stuff, it can be easy to dismiss the fact that you didn't import the correct namespace. Doh!
Secondly, I found out that you can just call the dialog.getTag() in the onDialogPositiveClick(DialogFragment dialog). You can check that against whichever dialog it is you show. In this way, you can have multiple dialogs using the same interface methods, and just incorporate your "checking" logic there.
ie, I have this line:
dialog_save.show(getFragmentManager(), "Dialog_save");
So I can simply do this to check which dialog I clicked the positive button from:
#Override
public void onDialogPositiveClick(DialogFragment dialog) {
// User touched the dialog's positive button
if (dialog.getTag().equals("Dialog_save"){
//..do stuff...
}
if (dialog.getTag().equals("Dialog_cancel"){
//..do stuff...
}
}
Related
I have this code separate class which makes a Snackbar to be displayed within my application, But with my current implementation I am getting a 'java.lang.NullPointerException'. How do I implement it in my main class properly?
here is my snack bar class:
public class SnackBarUtils
{
private static SnackBarUtils mInstance = null;
private Snackbar mSnackBar;
private SnackBarUtils()
{
}
public static SnackBarUtils getInstance()
{
if (mInstance == null)
{
mInstance = new SnackBarUtils();
}
return mInstance;
}
public void hideSnackBar()
{
if (mSnackBar != null)
{
mSnackBar.dismiss();
}
}
public void showProblemSnackBar(final Activity activity, final String message)
{
mSnackBar = Snackbar.make(activity.findViewById(android.R.id.content), message,
Snackbar.LENGTH_INDEFINITE);
// Changing action button text color
View sbView = mSnackBar.getView();
TextView textView = sbView.findViewById(com.google.android.material.R.id.snackbar_text);
mSnackBar.setAction("x", new View.OnClickListener()
{
#Override
public void onClick(View v)
{
//Call your action method here
mSnackBar.dismiss();
}
});
textView.setTextColor(Color.WHITE);
sbView.setBackgroundColor(Color.RED);
textView.setMaxLines(3);
mSnackBar.show();
}
}
This is my current implementation within main activity, I have already Initialized the snackbar class like this:
SnackBarUtils snackBarUtils;
and then called it like this:
snackBarUtils.showProblemSnackBar(MainActivity.this, mPlainTextResponse);
what am I doing wrong? Or what is the correct way to do this?
First of all, you would share the stacktrace of the NPE for more context.
For the snackbar utility:
If you are using callbacks, then you can use the utility for displaying a snackbar with that callback as parameter:
interface onProblemSnackbarClickedListener {
void onActionClicked(View view);
}
...
/* inside SnackBarUtils.java */
...
public static void showProblemSnackbar(View view, #StringRes int message, onProblemSnackbarClickedListener listener){
Snackbar mSnackBar = Snackbar.make(view,message,Snackbar.LENGTH_INDEFINITE)
.setAction("x", new View.OnClickListener() {
#Override
public void onClick(View v) {
listener.onActionClicked(v);
mSnackBar.dismiss();
}
})
mSnackbar.show();
}
The callback could work for the need to listen to it in the activity/fragment.
For the styling of the Snackbar, you can see this related question:
Style SnackBar in theme app.
Keep in mind the migration from "Support design" to MDC (Material design components), that facilitates the global styling of the snackbar with theme attributes.
Right now i'm having :-
1) 1 activity which is the main activity that extends from AppCompactActivity.
2) 1 fragment class that extends from fragment, this is the fragment that being called from main activity (1) - ProfileTeacherActivity.java
3) 1 fragment class that extends from DialogFragment, this dialog getting called from fragment (2) - ModalBox.java
So, basically, this is just a simple flow of execution. At start, the applications showing the main activity (1) having drawer that have a few links as example a profile link, when click this link, the application call the fragment (2) showing details of profile with one edit button. After clicking edit button, the applications will invoke DialogFragment (3) that contains some of EditText for editing user's profile.
What i want to achieve is, after editing user's profile and successful saved into database, i tried to send user's data back to fragment (2) just to show latest updated info, unfortunately it didn't work.
Here is what i'm tried :
1) Creating Interface inside DialogFragment (3) - ModalBox.java
public class ModalBox extends DialogFragment{
....
public interface EditProfileModalBoxInterface {
void onFinishEditProfile( HashMap<String, String> dataPassing );
}
...
...
}
2) Inside DialogFragment also i have .setPositiveButton function for OK button. - ModalBox.java
public class ModalBox extends DialogFragment{
...
...
public Dialog onCreateDialog(Bundle savedInstanceState ) {
...
builder
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(final DialogInterface dialog, int id) {
// At here i'm using retrofit2 http library
// to do updating stuff
// and inside success's callback of retrofit2(asynchronous)
// here i call the below function to send data
// dataToSend is a HashMap value
sendBackResultToParent( dataTosend );
}
})
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// User cancelled the dialog
}
});
.....
}
// Function called inside success's callback of retrofit2
public void sendBackResultToParent( HashMap<String, String> data ) {
// instantiated interface
EditProfileModalBoxInterface ls=(EditProfileModalBoxInterface)getTargetFragment();
// declaring interface's method
ls.onFinishEditProfile( data );
}
}
3) Finally, i'm implements those interface inside fragment (2) - ProfileTeacherActivity.java
public class ProfileTeacherActivity extends Fragment
implements ModalBox.EditProfileModalBoxInterface{
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState ) {
.....
.....
}
// At here the interface's method did't triggered
#Override
public void onFinishEditProfile( HashMap dataPassedFromDialog ) {
Toast.makeText(getActivity(), "Testing...." , Toast.LENGTH_LONG).show();
}
}
What i'm confuses right now is, the problem happens only when i called this function sendBackResultToParent( dataTosend ); inside retrofit2 success's callback, it does triggered when calling outside of it. I'm assumed the async called caused this. If i could use Promise or something like that or is there any workaround on this?
The following existing solutions didn't work in my case :
Callback to a Fragment from a DialogFragment
How to send data from DialogFragment to a Fragment?
Send Data from DialogFragment to Fragment
Ask me for more inputs if above use case didn't clear enough or misunderstanding. Thanks for the helps. Regards.
This is a sample DialogFragment code used to send message to selected contact. I too required to capture the click event on the DialogFragment and redirect.
Ideally to achieve this , this is what needed to be done
Override the positive/negative button clicks of AlertDialog.Builder and do no action
After this , using getButton method mention AlertDialog.BUTTON_NEGATIVE or AlertDialog.BUTTON_POSITIVE and assign an action
public class SMSDialogFrag extends DialogFragment {
private static String one="one";
private EditText messageContent;
private AlertDialog dialog;
private String mobNumber;
public static SMSDialogFrag showDialog(String mobNumber){
SMSDialogFrag customDialogFrag=new SMSDialogFrag();
Bundle bundle=new Bundle();
bundle.putString(one, mobNumber);
customDialogFrag.setArguments(bundle);
return customDialogFrag;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
View view = getActivity().getLayoutInflater().inflate(R.layout.sms_dialog, null);
alertDialogBuilder.setView(view);
setupUI(view);
alertDialogBuilder.setTitle("");
alertDialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
//Do nothing here because we override this button later
}
});
alertDialogBuilder.setPositiveButton("Send", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
//Do nothing here because we override this button later
}
});
dialog = alertDialogBuilder.create();
dialog.show();
dialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
dialog.dismiss();
//else dialog stays open. Make sure you have an obvious way to close the dialog especially if you set cancellable to false.
}
});
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
sendMessage();//IN YOUR USE CASE YOU CAN REDIRECT TO YOUR CALLER FRAGMENT
}
});
return dialog;
}
void setupUI(View view){
TextView textViewMob=(TextView)view.findViewById(R.id.mobNumber);
messageContent=(EditText)view.findViewById(R.id.messageContent);
mobNumber=getArguments().getString(one);
textViewMob.setText("Send message to : "+mobNumber);
}
void sendMessage(){
if( ! TextUtils.isEmpty(messageContent.getText())){
try {
SmsManager smsManager = SmsManager.getDefault();
Log.v(Constants.UI_LOG,"Number >>>>>>> "+mobNumber);
smsManager.sendTextMessage(mobNumber, null, messageContent.getText().toString(), null, null);
} catch (Exception e) {
e.printStackTrace();
}
Toast.makeText(getActivity(),"Message Sent!",Toast.LENGTH_SHORT).show();
dialog.dismiss();
}else{
Toast.makeText(getActivity(),"Please enter message to send!",Toast.LENGTH_SHORT).show();
}
}
}
Consider using eventBus, for example
Otto
The usage is very simple. All you need to do is create an evenbus:
public static Bus bus = new Bus(ThreadEnforcer.MAIN); //use Dagger2 to avoid static
Then create a receiver method (in fragment 2 in your case):
#Subscribe
public void getMessage(String s) {
Toast.makeText(this, s, Toast.LENGTH_LONG).show();
}
Send you message by calling(from DialigFramgent):
bus.post("Hello");
And don't forget to register your eventBus inside onCreate method(of your Fragment):
bus.register(this);
And that is!
From architectural standpoint, 2 fragments should not directly communicate with one another. Container Activity should be responsible for passing data between it's child fragments. So here's how i would do it:
Implement your interface in the container Activity and just attach your interface implementation in the Activity to the Dialog class and call that interface method when required. Something like this :
public static class ModalBox extends DialogFragment {
EditProfileModalBoxInterface mListener;
// Container Activity must implement this interface
public interface EditProfileModalBoxInterface {
void onFinishEditProfile( HashMap<String, String> dataPassing );
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (EditProfileModalBoxInterface) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement EditProfileModalBoxInterface");
}
}
}
Then call mListener.onFinishEditProfile(....) where ever it's required in the DialogFragment class.
This way you will receive the result back in your Activity class from where you can call your desired fragment's relevant method to pass the results to that fragment.
This whole flow has been described here
Finally the culprit founds. All the answers mentioned above were right. And my script also actually works, the problem is related with my API json's response that did't coming with right structure. In my case, i'm using retrofit2 with GSON converter for parsing into POJO. Found the info on log saying about :
Expected BEGIN_OBJECT but was BEGIN_ARRAY
Means that, GSON expecting the json object, which is my API was returned JSON array. Just change the API's structure then all goods to go. Thanks guys for your hard time. Really appreciated, as i'm right now knowing how to deal with eventBus, replacing default OK and Cancel button and the correct way for communicating between fragments.
i've this dialog
case DIALOGO_EDIT:
final EditText editText = new EditText(context);
builder.setView(editText);
builder.setPositiveButton("Send", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int i) {
}
});
builder.setNegativeButton("Close", null);
break;
But when i rotate the device the dialog dismiss... How can i solve this problem and mantein the dialog during rotation? Or display the edit text at "full screen" like WhatsApp
When the screen is rotated, the activity actually is restarted and killed, so you need to save and be able to restore the data using the lifecycle methods. Have a look here: Saving Persistent State
You may want to have a look to this question here
The best way of avoid this problem is to use DialogFragment.
Create a new class extended to DialogFragment. Override onCreateDialog and return your old Dialog or an AlertDialog.
Them you can show it with DialogFragment.show(fragmentManager, tag).
Here an example with the activity like listener:
public class MyDialogFragment extends DialogFragment {
public interface YesNoListener {
void onYes();
void onNo();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof YesNoListener)) {
throw new ClassCastException(activity.toString() + " must implement YesNoListener");
}
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.dialog_my_title)
.setMessage(R.string.dialog_my_message)
.setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((YesNoListener) getActivity()).onYes();
}
})
.setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
((YesNoListener) getActivity()).onNo();
}
})
.create();
}
}
And in the Activity you call:
new MyDialogFragment().show(getSupportFragmentManager(), "tag"); // or getFragmentManager() in API 11+
This kind of questions already asked,and there are also solution for it, these three questions are matched with your problem (and they answered):
Android Best way of avoid Dialogs to dismiss after a device rotation
Android DialogFragment vs Dialog
How can I show a DialogFragment using compatibility package?
I have an Activity named PhotoSelectorActivity. It inherits from a BaseActivity that looks like this:
public class BaseActivity
extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(this.getClass().getSimpleName(),
"onCreate("+Integer.toHexString(System.identityHashCode(this))+")");
}
#Override
protected void onDestroy() {
super.onDestroy();
getSupportActionBar().setCustomView(null);
Log.d(this.getClass().getSimpleName(),
"onDestroy("+Integer.toHexString(System.identityHashCode(this))+")");
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
return onNavigateUp(item);
case R.id.menu_item_settings:
startActivity(new Intent(this, PreferencesActivity.class));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#TargetApi(Build.VERSION_CODES.HONEYCOMB)
protected ActionBar setupActionBar(boolean enableBackButton) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
getSupportActionBar().setDisplayHomeAsUpEnabled(enableBackButton);
}
ActionBar actionBar = getSupportActionBar();
actionBar.setCustomView(R.layout.action_bar);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
return null;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
The purpose of this BaseActivity is to provide the same menu and actionbar to each one of my activities. You'll notice the getSupportActionBar().setCustomView(null) in the onDestroy() method, that's there to try and combat the problem that I may be having.
When i get an orientation change event, i notice in DDMS that i end up with 2 instances of my activity. One of them may be leaking, but I'm not certain. Here's a screen shot from DDMS:
So the object at the top is the Activity in question: PhotoSelectorActivity. The instance shown here is the previous instance (onDestroy() has already been called on it). Yet it remains in memory even after a forced GC via DDMS.
Another bit of information is that this only seems to happen after using a dialog. That is, when the Activity is initially displayed and before the user performs and action I can do back to back orientation changes without the # of activities climbing above 1. After I've used the following dialog i seem to get the extra Activity in memory:
public class PhotoSourceDialog
extends DialogFragment
implements DialogInterface.OnClickListener {
public static interface PhotoSourceDialogListener {
void onPhotoSourceSelected(String result);
}
private PhotoSourceDialogListener listener;
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!PhotoSourceDialogListener.class.isInstance(activity)) {
throw new IllegalStateException(
"Activity must implement PhotoSourceDialogListener");
}
listener = PhotoSourceDialogListener.class.cast(activity);
}
#Override
public void onDetach() {
super.onDetach();
listener = null;
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle(R.string.photo_source)
.setItems(R.array.photo_sources, this).create();
}
#Override
public void onClick(DialogInterface dialog, int which) {
String choice = getResources().getStringArray(
R.array.photo_sources)[which];
if (listener!=null) {
listener.onPhotoSourceSelected(choice);
}
}
}
and to invoke it i do this in my activity:
PhotoSourceDialog dialog = new PhotoSourceDialog();
dialog.show(getSupportFragmentManager(), PhotoSourceDialog.class.getName());
So my question is this: Should I be worried? Is this just something that is hanging around for a bit but will eventually be GCd? I would think that if there was a leak it would grow higher than 2.
I'm closing this question. Someone at google has responded with the following:
OK, in that case then it's not an AppCompat bug since the standard
Action Bar implementation is used on ICS+.
Looking at that MAT screenshot, the framework's ActionMenuItemView is
being referenced from a clipboard event which is being finalized,
hence about to be GC'd. The LayoutInflater is probably the
LayoutInflater that the Activity keeps itself (getLayoutInflater()).
code reuse for activity is confusing. In normal situation we can design a parent activity and put all common method in it. like follow :
public class BaseActivity extends Activity{
#Override
protected void doExit() {
showDialog(DIALOG_EXIT_ALTER);
}
protected Dialog onCreateDialog(int id, Bundle args) {
switch (id) {
case DIALOG_EXIT_ALTER:
return new AlertDialog.Builder(BaseUIActivity.this)
.setTitle("Exit?")
.setPositiveButton("Yes",
new DialogInterface.OnClickListener() {
public void onClick(
DialogInterface dialoginterface, int i) {
close();
}
})
.setNeutralButton("No",null).create();
default:
return null;
}
}
protected void close() {
finish();
}
}
then other activities extend BaseActivity will show a alertdialog instead of exit immediately when back button press.
But in android framework there are more than one build-in activites such like PreferenceActivity,ListActivity,ActivityGroup,etc.
if my activity extend those activities then it can't use the common code defined in BaseActivity.because of Java's single inheritance.
So is there other way recommend to do code reuse for activity in android?
Create a new class ActivityHelper.
public class ActivityHelper {
Activity activity;
public ActivityHelper(Activity activity) {
this.activity = activity;
}
public Dialog onCreateDialog(int id, Bundle args) {
// do many usefull things
return result;
}
}
Use it in all your activities.
protected Dialog onCreateDialog(int id, Bundle args) {
return activityHelper(id, args);
}
Since PreferenceActivity, ListActivity, ActivityGroup are specialized form of Activity, and you have to use them in their relative context.
So IMHO workaround is to have one copy of Base+[all above Activity] if you have to use them more then one time in your project, and extend your child ListActivity or whatever Specialized Activity it is.