I am trying to include a DialogFragment as a destination, following the examples here, adapting the code for Xamarin.
The code works fine as long as I use <fragment/> in the navgraph. However, when changed to <dialog/>, the app crashes when I try navigating to the DialogFragment, with an error saying: AndroidX.Fragment.App.Fragment+InstantiationException: 'Unable to instantiate fragment fragmentname: make sure class name exists.
Is this functionality not available in xamarin.android, or are there any additional steps I need to take to make it work?
EDIT 1:
Posting example code.
MainActivity.cs:
namespace exampleApp
{
[Activity(Label = "#string/app_name", Theme = "#style/AppTheme.NoActionBar", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
SetContentView(Resource.Layout.activity_main);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:id="#+id/main_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/nav_graph"
app:defaultNavHost="true" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
fragment1.cs:
using Android.OS;
using Android.Views;
using Android.Widget;
using System;
using AndroidX.Fragment.App;
using AndroidX.Navigation;
namespace exampleApp
{
public class Fragment1 : Fragment
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//Inflating view pretty much creates it in memory, without showing it on screen.
View view = inflater.Inflate(Resource.Layout.fragment1_layout, container, false);
return view;
}
public override void OnViewCreated(View view, Bundle savedInstanceState)
{
base.OnViewCreated(view, savedInstanceState);
Button dialogButton = view.FindViewById<Button>(Resource.Id.button_dialog);
dialogButton.Click += (object sender, EventArgs e) =>
{
Navigation.FindNavController(view).Navigate(Resource.Id.dest_dialog);
};
}
}
}
fragment1_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment1_layout">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Open Dialog"
android:id="#+id/button_dialog"/>
</LinearLayout>
ExampleDialog.cs:
namespace exampleApp
{
public class ExampleDialog : AndroidX.Fragment.App.DialogFragment
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
return inflater.Inflate(Resource.Layout.exampleDialog_layout, container, false);
//return base.OnCreateView(inflater, container, savedInstanceState);
}
}
}
exampleDialog_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/exampleDialog_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="EXAMPLE DIALOG"/>
</LinearLayout>
nav_graph.xml:
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_graph"
app:startDestination="#+id/dest_fragment1">
<fragment
android:id="#+id/dest_fragment1"
android:name="exampleApp.Fragment1">
</fragment>
<dialog
android:id="#+id/dest_dialog"
android:name="exampleApp.ExampleDialog">
</dialog>
</navigation>
#kfjpkcw
I tried to reproduce your problem and was successful. For some reason xamarin.android doesn't seem to be able to distinguish between a dialog and a fragment, hence the exception. I reckon you should report that at https://github.com/xamarin/AndroidX/issues. I'm sure I tried a dialog ages ago and it worked, (https://medium.com/androiddevelopers/navigation-component-dialog-destinations-bfeb8b022759 but maybe I only used it in Android Studio) but after working with nav_graphs for a while I found it tedious adding all the extra stuff like a destination to goto and animations (then stuck with one type of animation), so decided to only use nav_graph for fragment part only. If you read my docx file I sort of justify why I've gone my own route.
To get around your problem with dialogs, just handle it as I do in NavigationGraph series I referred to.
Great to see another Xamarin.Android developer using the NavigationComponent!!
That error is usually because you have a typo in your nav_graph.xml. You usually use the fully qualified name of the fragment or dialog - case is important. I always lowercase the namespace as in com.companyname.navigationgraph6.fragments.RaceResultFragment. The RaceResultFragment (fragment name) is exactly as it is declared in the class.
I have a number of tutorials on the NavigationComponent, NavigationGraph1 through NavigationGraph6 which increase in complexity as the project number increases. Each project has the same NavigationGraph.docx explaining each feature as I progress the tutorials.
You can find them at https://github.com/gmck. From memory, I think it is NavigationGraph3 where I introduce a dialog.
I was able to work around this issue by applying a Register attribute to the dialog fragment. It doesn't seem necessary to add it to Fragments in Xamarin, but with DialogFragments it seems to be necessary.
[Register("com.mycompany.dialogs.AlertDialogView")]
public class AlertDialogView : DialogFragment { }
Then of course within the nav graph, just set the name to the registered name.
<dialog
android:id="#+id/alert_dialog_fragment"
android:name="com.mycompany.dialogs.AlertDialogView">
</dialog>
Related
Someone please help this is killing me!!! I have a textview which I am trying to et working for some reason it does not update? the text does not change the tabs are just blank.
NOTE: there are no errors or anything tabs are working fine, bur the text never gets updated I cannot seem to find out why please help!!
Thanks in advance
Main.cs:
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
namespace App1
{
[Activity(Label = "ActionBar Tabs Example")]
public class Main : Activity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.TabbedMenuPage);
//enable navigation mode to support tab layout
this.ActionBar.NavigationMode = ActionBarNavigationMode.Tabs;
//adding audio tab
AddTab("Audio", Resource.Drawable.Icon, new AudioFragment());
//adding video tab
AddTab("Video", Resource.Drawable.Icon, new VideoFragment());
}
/*
* This method is used to create and add dynamic tab view
* #Param,
* tabText: title to be displayed in tab
* iconResourceId: image/resource id
* fragment: fragment reference
*
*/
void AddTab(string tabText, int iconResourceId, Fragment fragment)
{
var tab = this.ActionBar.NewTab();
tab.SetText(tabText);
tab.SetIcon(iconResourceId);
// must set event handler for replacing tabs tab
tab.TabSelected += delegate (object sender, ActionBar.TabEventArgs e) {
e.FragmentTransaction.Replace(Resource.Id.fragmentContainer, fragment);
};
this.ActionBar.AddTab(tab);
}
class AudioFragment : Fragment
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = inflater.Inflate(Resource.Layout.Tab, container, false);
var sampleTextView = view.FindViewById<TextView>(Resource.Id.textView);
sampleTextView.Text = "This is Audio Fragment Sample";
return view;
}
}
class VideoFragment : Fragment
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = inflater.Inflate(Resource.Layout.Tab, container, false);
var sampleTextView = view.FindViewById<TextView>(Resource.Id.textView);
sampleTextView.Text = "This is Video Fragment Sample";
return view;
}
}
}
}
Tabaxml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/textView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textStyle="bold"
android:textSize="16dp"
android:background="#ffe3e1e1"
android:textColor="#ff393939" />
TabbedMenu.axml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="#+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="0dip"
/>
</LinearLayout>
Your issue is here:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<FrameLayout
android:id="#+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="0dip"
/>
</LinearLayout>
Your FrameLayout has a height of 0! That means all the content inside it is hidden. Set the layout_height to something else such as match_parent.
Also, side note, fill_parent is deprecated now. You should use match_parent.
I am unable to get a fragment to update after changing data. I call
getFragmentManager().beginTransaction().detach(this).attach(this).commit();
but onCreateView does not get called. I tried several different ways in refreshFragment(). I do see messages for "I/InjectionManager: dispatchCreateOptionsMenu" after I put in the above line of code so something is happening, but onCreateView() is not getting called.
public class MainActivityFragment extends Fragment {
private static Logger logger = Logger.getLogger(MainActivityFragment.class);
String poolWord = "ABCDE";
String newWord = "";
public MainActivityFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
logger.info("onCreate------------------------");
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
logger.info("onCreateView------------------------");
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_main, container, false);
GridView gridViewPool = (GridView) view.findViewById(R.id.gridviewPool);
gridViewPool.setAdapter(new LetterImageAdapter(getActivity(), poolWord));
gridViewPool.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
logger.error("po " + position);
updateWords(position);
refreshFragment();
}
});
GridView gridviewNewWord = (GridView) view.findViewById(R.id.gridviewNewWord);
gridviewNewWord.setAdapter(new LetterImageAdapter(getActivity(), ""));
return view;
}
private void refreshFragment() {
logger.info("refreshFragment------------------------");
getFragmentManager().beginTransaction().detach(this).attach(this).commit();
// getFragmentManager().beginTransaction().replace(R.id.fragment, this).attach(this).commit();
// Fragment fragment = getFragmentManager().findFragmentById(R.id.fragment);
//getFragmentManager().beginTransaction().detach(fragment).attach(fragment).commit();
getFragmentManager().executePendingTransactions();
}
After several hours, I got it to work. Not sure of all the details as this is my first android project. Posting an answer in case anybody else has the same problem.
The crux of the problem, I suspect, is the generated code from Intellij did not start a transaction in the Activity class. It did not include
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new MainActivityFragment())
.commit();
}
I'm not sure how my MainActivityFragment was created without this, but it was.I also replaced the generated activity-main.xml with one I had from a tutorial. The one from Intellij did not have an android:id that I could use for the container object. It also contained other stuff I did not need at this time.The new activity-main.xml looks like this:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:id="#+id/container"
android:layout_width="match_parent" android:layout_height="match_parent"
tools:context=".MainActivity" tools:ignore="MergeRootFrame"/>
The old generated one was:
<android.support.design.widget.AppBarLayout
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:theme="#style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/AppTheme.PopupOverlay"/>
</android.support.design.widget.AppBarLayout>
<include layout="#layout/content_main"/>
</android.support.design.widget.CoordinatorLayout>
There was also a content-main.xml which I got rid of. That may be what android used to generate my MainActivityFragment without explicit code. Someone more experienced with android development may be able to answer that.
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/fragment"
android:name="com.ultramondo.mywordandroid.MainActivityFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
tools:layout="#layout/fragment_main">
</fragment>
I'm sure there's some useful stuff I can go back to but for now I want simplicity. I also had to make some changes to use android.support.v4.app packages for Fragment and Transaction classes. It's kind of like coming in the middle of a movie and figuring out what the plot is. Once I see the whole movie, I'm sure it will become clearer.
So, I'm following the steps in the Android API to add login to my app. My app is already fairly developed, and of course I'm no to android, so I'm having issues at this point.
the step reads:
Then set up the button in your UI by adding it to a fragment and update your activity to use your fragment.
I've searched for how to add a fragment and anything about that, watched youtube videos, but can't find out exactly what this means. Can anyone dumb this down for me?
https://developers.facebook.com/docs/facebook-login/android
First, you need a hosting activity, extending AppCompatActivity that has a FrameLayout in its related layout file. Then you inflate the layout in the onCreate method and add the needed Fragment:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (findViewById(R.id.fragment_placeholder) != null) {
if (savedInstanceState != null) {
return;
}
// we are extending AppCompatActivity, so we need supportFragmentManager
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_placeholder, new ContentFragment()).commit();
}
}
}
The according layout file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>`
Now you need to define ContentFragment:
public class RecordFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
return inflater.inflate(R.layout.fragment_content, container, false);
}
}
The last step is the layout file for our fragment, fragment_content, containing a Button:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="#+id/button"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
For more information why we use AppCompatActivity, read this answer.
I have an activity that hosts two fragments. One fragment provides some info to the user in various fields. The other fragment should show a Map.
For the fragment hosting the map I need to be able to add custom logic for displaying markers and several other things that I would like to reuse in other places in the app. What is the best way to create a map without defining the fragment in xml like:
<fragment
class="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Thanks,
Nathan
Edit:
I am now using nested fragments, not sure if that is the best way to handle this? My mapfragment is now:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="#+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment" />
</LinearLayout>
and the Fragments code is:
public class ViewCurrentMapFragment extends Fragment implements View.OnClickListener {
public static final String TAG = "MAP_FRAGMENT";
private GoogleMap map;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_view_current_map, container, false);
return rootView;
}
private void getCurrentMarkers() {
// Getting reference to the SupportMapFragment of activity_main.xml
SupportMapFragment smf = ((SupportMapFragment) getActivity().getSupportFragmentManager().findFragmentById(R.id.map));
map = smf.getMap();
}
#Override
public void onClick(View v) {
}
#Override
public void onResume() {
super.onResume();
getCurrentMarkers();
}
}
The problem I am having now is that smf.getMap() is returning null. Any ideas?
Edit2:
Got it to work by changing:
com.google.android.gms.maps.MapFragment
to
com.google.android.gms.maps.SupportMapFragment
Maybe:
<fragment
class="com.mycompany.MyCustomFragmentExtendingMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
or nest fragments like here: http://code.google.com/p/gmaps-api-issues/issues/detail?id=5064#c1
The Fragments in my app are not re-added to the activity's view when the device is rotated. If i understand the documentation (and similar questions here on SO) correctly, this should be handled be the fragment manager (without having to specify android:configChanges in my manifest).
My Activity looks like:
public class MainActivity extends SherlockFragmentActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null){
getSupportFragmentManager().beginTransaction()
.add(R.id.main_fragment_placeholder, new DashboardFragment(),"dashboard")
.commit();
}
}
}
And a Fragment looks like (omitted clicklisteners and so on for clarity):
public class DashboardFragment extends SherlockFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_dashboard, container, false);
}
}
The layout files look like (activity_main.xml):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/main.main">
<FrameLayout android:id="#+id/main_fragment_placeholder"
android:layout_height="fill_parent"
android:layout_weight="1"
android:layout_width="0px"
/>
</LinearLayout>
And fragment_layout.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main.wrapper"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<my.layout.DashboardLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#+id/main.dashboard">
<!-- some buttons here -->
</my.layout.DashboardLayout>
</LinearLayout>
However, when i rotate my device the screen always becomes blank.
Turns out I did a Stupid Thing. I overrode the onSaveInstanceState(...) method in my activity, without calling super.onSaveInstanceState(), which resulted in the fragments not being recreated. Posting the answer here in hopes that I can save somebody else the time!
Fragments usually get recreated on configuration change. If you don't wish this to happen, use
setRetainInstance(true); in the Fragment's constructor(s)
This will cause fragments to be retained during configuration change.
http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)