Android, Xamarin: Open Dialog from Viewpager Fragment inside of surrounding activity - android

I've got a bit of a problem here.
Basically, I have an activity. In the centre of the activity, I inflate a Viewpager fragment with some buttons inside of it. So I have two classes. Now, When the user clicks on the button inside the viewpager, I need to open a custom dialog. I couldn't find a way to implement the custom dialog in within my viewpager class, so I implemented it in within my activity. It does work here when I open the dialog from any button in the activity EXCEPT for the ones that come from the viewpager. Since this is a bit complicated, here an image I drew nicely in the paint:
So, I decided to leave the dialog inside the activity and just open it up via the viewpager. This looked like this:
Fragment:
button1.Click += delegate
{
var xy = new Activity_Categories();
xy.click();
};
The activity (Activity_Categories):
public void click()
{
ShowDialog(0);
}
protected override Dialog OnCreateDialog(int id)
{
return new AlertDialog.Builder(this)
.SetIcon(Resource.Drawable.btn_bookoflifeMainMenu)
.SetTitle("test")
.SetPositiveButton("ok", this)
.SetNegativeButton("no", this)
.SetItems(choices, this)
.Create();
return base.OnCreateDialog(id);
}
public void OnClick(IDialogInterface dialog, int which)
{
Toast.MakeText(this, which.ToString(), ToastLength.Short).Show();
}
So, what I tried is to open the method "click" from the viewpager that is then opening the dialog. But I get this error:
Unhandled Exception:
Java.Lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.ApplicationInfo android.content.Context.getApplicationInfo()' on a null object reference
I'm guessing it has something to do with the context "this". But even when I defined a Context context, and in Oncreate went: context = this; . I got the same error.
So the two questions:
How could I open up the dialog from the viewpager fragment? OR
Why am I getting this error?
If you know a better way of doing it, I'd be more than happy to hear! :)
THANK YOU!

Related

Fragment not attached to host

Let me explain the whole thing, just in case. I'm using BottomNavigationView with Jetpack's Mobile Navigation graphs and so. I reimplemented the method:
NavigationUI.setupWithNavController(...)
during navigation setup, instead of using this method I made a very similar:
customSetupWithNavController(...)
the only changes I made was to change de default fading transition between fragments, and started to using more natural (in my opinion) side slide animations, just like this in onNavDestinationSelected:
if(origin == 0){
builder.setEnterAnim(R.anim.slide_in_from_right)
.setExitAnim(R.anim.slide_out_to_left)
.setPopEnterAnim(R.anim.slide_in_from_left)
.setPopExitAnim(R.anim.slide_out_to_right);
}else if(origin == 1){
builder.setEnterAnim(R.anim.slide_in_from_left)
.setExitAnim(R.anim.slide_out_to_right)
.setPopEnterAnim(R.anim.slide_in_from_right)
.setPopExitAnim(R.anim.slide_out_to_left);
}
Where origin stands for the direction where the incoming Fragment comes from.
It came with a problem: 2 of my fragments need a FAB to add elements to a recycler, and the side slide transitioning suddenly became ugly. So I added a single FAB in MainActivity's Layout, and a logic shows the FAB only when these 2 fragments are called.
I couldn't find a nice way to pass the click event from Activity to Fragments, because I wasn't able to instantiate the Fragments, since the Navigation handles the whole process.
So, what I did was to create a ViewHolder, since I know it can survive trough lifecycle changes. This ViewHolder holds an int, and a MutableLiveData, in the MainActivity logic I pass the current selected id of the element selected by the BottomNavigationView to the int, and only if the MainActivity's FAB is clicked the live Boolean is set to true. So, in Fragments onViewCreated() I added and observer to this Boolean, when the value is set to true, and the id passed to the ViewHolder matches with the id of the current fragment, the Boolean is set back to false, and something can be done, it's something like this:
eventsNotificationHandler.getClickEvent().observe(requireActivity(), new Observer<Boolean>() {
#Override
public void onChanged(Boolean aBoolean) {
if(aBoolean && eventsNotificationHandler.getPositionId() == R.id.nav_contacts){
eventsNotificationHandler.setClickEvent(false);
//do something here
}
}
});
This notificationHandler is the ViewHolder.
So far so good, at this point I can:
1- Navigate between BottomNavigationView' Fragments freely, and the FAB shows only for needed fragments.
2- Use Log.d(...) inside the observer any time I want, and see that the debug message just fine.
3- Toast a message, any time I want, ONLY if the context parameter was initialized outside the Observer, something like this:
Toast.makeText(previouslyDefinedContext, "SOME TEXT", Toast.LENGTH_SHORT).show();
What I can't:
1- Launch an Activity whenever I want, from inside the observer by using same idea than before, ONLY initializing the context before, and outside the Observer I was able to start the intent, just like this:
eventsNotificationHandler.getClickEvent().observe(requireActivity(), new Observer<Boolean>() {
#Override
public void onChanged(Boolean aBoolean) {
if(aBoolean && eventsNotificationHandler.getPositionId() == R.id.nav_contacts){
eventsNotificationHandler.setClickEvent(false);
Intent newContact = new Intent(previouslyDefinedContext, NewContactActivity.class);
startActivity(newContact);
requireActivity().overridePendingTransition(R.anim.slide_in_from_right,R.anim.slide_out_to_left);
}
}
});
But in this particular case I can launch the new Activity as many times as I want, BUT ONLY if I navigate directly to this particular fragment where the observer is defined after the app opens, if I decide to navigate first trough some other fragments instead, and then I go to this fragment to try to launch the Activity, the app crashes
I've noticed that this exact behavior happens when I call requireContext() from inside the Observer, it works but then stops working.
The app crashes with:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: cu.arrowtech.bpawreckproject, PID: 18019
java.lang.IllegalStateException: Fragment FragmentContacts{883259b} (9f127bdb-127d-4366-b90b-c8900a5a771e)} not attached to Activity
What I want:
1- A right way to launch a new Activity from inside a Fragment, by pressing a FAB in MainActivity, if it's possible.
2- A nice way to switch fragments if a possible solution implies to change the logic I have already.
3- Keep using Jetpack and Navigation Graphs.
I'm able to do what I want by using 2 separate FABs in each Fragment, but the transitioning is not nice and beautiful.
I'm open to suggestions, even if that implies to change the logic. I'm almost certain it must be a better way to do what I'm doing, instead of using ViewHolder for this purpose.
I would like to get something similar to the Google Pay, it seems to be that the Buttons for adding payment method, passes, and transfers is the same button, but it adapts to each situation.
After some reasearch I did found a way to keep fluid transitions between fragments AND Mobile Navigation components AND a single FAB in the MainActivity layout.
What I did was to use an Interface instead of ViewModels (I always knew that approach was wrong):
public interface SharedViewsInterface {
void onFabClicked();
}
So in my MainActivity I defined that interface as null, and created a method to define it according to the situation. My MainActivity looks like:
public class MainActivity extends AppCompatActivity {
//UI elements
private FloatingActionButton main_fab;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Same dode to get the custom animations
//.......
//Main and only Fab configuration
main_fab = findViewById(R.id.main_fab);
main_fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(sharedViewsInterface !=null){
sharedViewsInterface.onFabClicked();
}
}
});
}
//Other auxiliary methods
public void setInterface(SharedViewsInterface sharedViewsInterface){
this.sharedViewsInterface = sharedViewsInterface;
}
}
By doing this, I can, from each fragment to implement the Interface in onCreate by doing this:
((MainActivity) requireActivity()).setInterface(new SharedViewsInterface() {
#Override
public void onFabClicked() {
Intent newContact = new Intent(requireContext(), NewContactActivity.class);
startActivity(newContact);
requireActivity().overridePendingTransition(R.anim.slide_in_from_right_short,R.anim.slide_out_to_left_medium);
}
});
This works well becouse the FAB is shown only when a fragment with Interface implementation is visible, see my example gif

DialogFragment dismiss() does not pop backstack

I have a simple DialogFragment that calls dismiss when exits, according to the documentation:
public void dismiss()
Dismiss the fragment and its dialog. If the fragment was added to the
back stack, all back stack state up to and including this entry will
be popped. Otherwise, a new transaction will be committed to remove
the fragment.
however, I found that the fragment is still on the backstack after calling dismiss() so I have to click back button to clear it. Does anyone know why ?
here's my code:
public void onCreate(Bundle b) {
super.onCreate(b);
setContentView(R.layout.test_layout);
class MyDialogFragment extends DialogFragment implements OnClickListener{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.hello_world, container, false);
Button b = (Button)v.findViewById(R.id.btn);
b.setOnClickListener(this);
return v;
}
#Override
public void onClick(View v) {
dismiss();
}
}
getFragmentManager().beginTransaction().add(android.R.id.content, new MyDialogFragment(), "test").addToBackStack("b").commit();
}
#Override
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0 ){
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
}
I also found out that if I don't override onBackPressed(), the back button simple doesn't work, no matter how many fragments I add to the activity, the back button always exits the activity right away.
I can confirm what #Luksprog said in his comment: the dialog must be started through show(FragmentTransaction, String).
Note after looking the source: make sure to call addToBackStack(String) on the supplied transaction or else it still won't work.
That it's a wrong way to create a DialogFragment.
Never ever use the FragmentManager to show a DialogFragment. To be shown there are a method called show(FragmentTransacion, String).
In java:
MyDialogFragment mDialogFragment = new MyDialogFragment();
mDialogFragment.show(getFragmentManager(), "MyDialogFragment");
For another hand, to dismiss the dialog just do this:
mDialogFragment.dismiss()
Another think that I would like to highlight is that the MyDialogFragment class is defined inner onCreate method :'(
Please, define the class outside the method or in another file if you want :)
Good Look!
dismiss()
findNavController().navigate(FirstBottomSheetDialogDirections.actionFirstSheetToSecondSheet())
This code is always the wrong thing to do: dismiss() is an asynchronous operation that doesn't actually dismiss anything immediately. That is unlike the navigate() which does immediately update the NavController's state, stacking the new dialog destination on top of the previous one.
This means that when the asynchronous dismiss actually happens, it correctly removes the dialog and, because it is a navigation stack, removes everything on top of it - including your second dialog. However, due to a bug in the DialogFragmentNavigator, we don't actually dismiss that second dialog, which is why it appears to work, despite everything actually already being internally out of sync (thus causing the later crash).
The correct way to pop a destination and navigate to a new destination as an atomic, immediate operation is to use popUpTo and popUpToInclusive. Therefore you can fix the sample app by removing the call to dismiss() and updating the action to pop the first dialog as part of the navigate call:
<action
android:id="#+id/action_firstSheet_to_secondSheet"
app:destination="#id/secondSheet"
app:popUpTo="#id/firstSheet"
app:popUpToInclusive="true"/>
This correctly pops the first dialog off the back stack and then navigates to the new dialog destination.
please refer this link : https://issuetracker.google.com/issues/191073055

How can I re use my alert DialogFragment for a result?

I recently created a standard list DialogFragment to build an AlertDialog in my Activity as can be seen as the answer here:
What is the best way to recreate an AlertDialog when the screen is rotated?
Now I would like to re-use this fragment for 3 different "Pop Up" selection lists in my activity. For each of the three buttons I need to identify the calling button to determine what action to take when the item from the list is selected.
What is the best way to achieve this?
Currently I am thinking that I need to pass the calling button ID to the DialogFragment and then pass it back to the activity with the result when the dialog completes. Is there a better way to achieve this goal?
I think probably the easiest way to achieve what you're going for is to just have three different listeners inside of your DialogFragment, and then have setters for each. Then when you build the alert dialog as a fragment, you can define what the onClick method for each listener will do in the calling method. So something like this:
protected DialogInterface.OnClickListener mListener1;
protected DialogInterface.OnClickListener mListener2;
protected DialogInterface.OnClickListener mListener3;
public void setListener1(final YourDialogFragment.OnClickListener passedListener) {
mListener1 = new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int which) {
listener.onClick(getActivity(), dialog, which);
}
};
}
Then inside of the code that calls the DialogFragment, call something like:
// Building the Dialog Fragment here
YourDialogFragment.setListener1(new YourDialogFragment.OnClickListener() {
#Override
public void onClick(FragmentActivity activity, DialogInterface dialog, int which) {
// Whatever you want to happen when you click goes here
}
});
Ideally you make some sort of helper to just take parameters so you're not explicitly calling the set methods from an activity, but that's the gist of it.
I would recommend you to show the dialog fragment from another fragment where you can implement the onClick listeners and use setTargetFragment() to tell the dialog fragment that who it is working with..
dialogFragment.setTargetFragment(this, 0);
and use getTargetFragment() to get the parent fragment from DialogFragment.
here is some code snippets from sample programs..
// Retrieve the progress bar from the target's view hierarchy.
mProgressBar = (ProgressBar)getTargetFragment().getView().findViewById(
R.id.progress_horizontal);
And also you can use setRetainInstance(true) in onCreate() method to tell the framework to try to keep this fragment around during a configuration changes
See this answer to get more idea, hope this helps..

How do I call a method of a running child activity from a parent activity?

I am developing an android app which has 3 tabs, created in MainActivity.java. Every tab has its own activity. In those activities I have a method called "Refresh()" to update the listview in that tab.
When the user clicks on a button the method "refreshTab(View v)" is called.
// Tab refreshen
public void refreshTab (View v) {
Activity MyActivity = this.getCurrentActivity();
MyActivity.Refresh();
}
This is throwing "The Method Refresh() is undefined for the type Activity. However, "MyActivity" is filled with the tab activity.
How would I go about getting this to work?
You need to cast the activity to your type of activity. Right now you are trying to call the Android class activity, which does not have a "Refresh" function.
Your button handler is a little over-complicated (even though it's only two lines)...
Just do something like:
// Tab refreshen
public void refreshTab (View v) {
Refresh();
}
If the way you've defined your OnClickListener is directly inline (but still within your activity's class), you may need to add a little direction, where MyClassType is the name of your class that extends Activity:
// Tab refreshen
public void refreshTab (View v) {
MyClassType.this.Refresh();
}

Is there a difference between Dialog.getContext() and the Activity that made the dialog?

Our application uses nested dialogs, and we've been successfully making one dialog that sits on top of another dialog by constructing it from the first dialog's getContext() method. So:
Activity:
//...
Dialog1 dialog = new Dialog1(this);
dialog.show();
//...
Dialog1:
//...
Dialog1(Context context) {
super(context);
//etc.
}
public void onSomeCondition() {
Dialog2 dialog2 = new Dialog2(getContext());
dialog2.show();
//etc.
}
However, there is a circumstance where we want to launch Dialog2 directly from the Activity while Dialog1 is still visible. So we put this method in the Activity:
public void onSomeOtherCondition() {
Dialog2 dialog = new Dialog2(this); //crunch
dialog.show();
//etc.
}
The window manager doesn't like this at all. So is it that getContext() is actually NOT exactly the same as the ContextWrapper-ness in the Activity? If so exactly how does this secondary context differ from the primary one, and if (for example) you passed back getContext() from a dialog to a calling Activity, would that create the same leak risk as holding on to a Context reference elsewhere can do?
If it's not the context, what's causing the problem?
I suspect the problem with starting Dialog 2 from Activity 1 when Dialog 1 is visible is because Dialog 1 (not Activity 1) is on the top of the activity stack. I'm by no means an expert, but I suspect that only the Activity at the top of the Activity stack can start new Activities.
I'm not entirely certain if the contexts are different (it would appear they are), but I suspect the problem is that you are not dismissing Dialog1 before attempting to start Dialog2 from your Activity. The WindowManager is probably angry because you are attempting to start a dialog on top of your Activity, but Dialog1 is already there.
Long story short, I think you need:
public void onSomeOtherCondition() {
this.dismiss();
mActivity.onSomeOtherCondition(); //we have a reference to the activity
}
EDIT
The solution I proposed in the comments is to pass Dialog1's context to mActivity.onSomeOtherCondition so that you can create Dialog2 with the context that is at the top of the stack.

Categories

Resources