I'm using fragment based navigation, each fragment has it's own toolbar.
When navigating to a fragment I want the back button to display in the toolbar.
I have overridden the OnCreateView method as follows:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var ignored = base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(_fragmentId, null);
_toolbar = view.FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
if (_toolbar != null)
{
ParentActivity.SetSupportActionBar(_toolbar);
ParentActivity.SupportActionBar.Title = _title;
ParentActivity.SupportActionBar.SetDisplayHomeAsUpEnabled(true);
_drawerToggle = new MvxActionBarDrawerToggle(
Activity,
(ParentActivity as MainView).DrawerLayout,
_toolbar,
Resource.String.drawer_open,
Resource.String.drawer_close);
(ParentActivity as MainView).DrawerLayout.AddDrawerListener(_drawerToggle);
}
return view;
}
SetDisplayHomeAsUpEnabled(true) should be changing the button to the back button, according to numerous other stack overflow answers, However this is not working as can be seen in the following screenshot:
I have checked that the SetDisplayHomeAsUpEnabled(true) line is hit when I navigate to the fragment.
For reference I am using Xamarin with MvvmCross.
How do I make change the toolbar to the up/back button when using fragment based navigation?
Solved it:
The DrawerToggle was somehow overriding the toolbar settings. Adding the following line fixes the problem.
_drawerToggle.DrawerIndicatorEnabled = false;
I'm not very familiar with Xamarin. Try setting the indicator with ParentActivity.SupportActionBar.SetHomeAsUpIndicator(Resource.Drawable.ic_menu_white_24dp);. This might help.
Related
I have inherited some code that requires a change in how it works. The original way didn't have the flexibility now required.
The application is a form generator, and hence has to create the UI on demand. This is Xamarin native, not Xamarin forms.
A FrameLayout for each form question is being created programmatically, added to the view, then a fragment is being added to this FrameLayout. All this is happening AFTER OnCreateView once the UI has been loaded to show a progress circle.
After working through a bunch of exceptions, I have become stuck with the exception
Java.Lang.IllegalArgumentException: No view found for id 0x50 (unknown) for fragment UploadFragment{a31e878 #7 id=0x50 upload_80}
My guess is that the FrameLayout doesn't exist when the fragment is trying to be displayed.
The exception occurs after the OnCreate() method runs after OnCreateView() completes.
I have not been able to find any code precedent for adding FrameLayouts programmatically with Fragments.
CODE Snippet
frame = new FrameLayout(this.Context);
frame.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
upload = new Widgets.UploadFragment(control, binding, Inflater, a, xFormInstance);
MainFormLayout.AddView(frame);
frame.Id = control.id;
fragmentTx.Add(frame.Id, upload, $"upload_{control.id}");
fragmentTx.Commit();
Any advice would be greatly appreciated. Thanks.
Extended Explanation
It may be a bit much to put in everything it does, but will try and put in as much as I can.
The Hierarchy of the page is
Activity -> FormFragment -> UploadFragment
So the parent of the UploadFragment is also a fragment, not the Activity.
Upload Fragment
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout>
<LinearLayout>
<TextView/>
<ImageButton/>
</LinearLayout>
<ImageView/>
</RelativeLayout>
</LinearLayout>
CODE
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Use this to return your custom view for this Fragment
_inflater = inflater;
v = _inflater.Inflate(Resource.Layout.BindImageInput, container, false);
SetUpload();
return v;
//return base.OnCreateView(inflater, container, savedInstanceState);
}
SetUpload() Sets the values of the label, the events for the buttons, and the image (if exists) to the imageview. It also deals with a few extra events to do with form event handling. Stopping SetUpload() from running still has the exception occur.
FormFragment
<RelativeLayout>
<TextView />
<View />
<ScrollView>
<LinearLayout />
</ScrollView>
</RelativeLayout>
CODE
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
ShowLoading();
View v = inflater.Inflate(Resource.Layout.Form2, container, false);
MainFormLayout = v.FindViewById<LinearLayout>(Resource.Id.mainFormView);
MainScrollView = v.FindViewById<ScrollView>(Resource.Id.mainScrollView);
formBuilderWorker = new BackgroundWorker();
return v;
}
OnResume() Calls the method where formBuilderWorker.DoWork() exists
formBuilderWorker.DoWork += delegate
{
Form.LoadForm(null, this, FormInstance);
}
LoadForm() uses a Interface to tell the FormFragment to display a control. One of which is the UploadFragment.
public void AddControl(Controls control, int? sectionID)
{
///CODE REMOVED FOR OTHER CONTROL TYPES (they still use old codebase)
Bindings binding = XForm.GetBindingForControl(control, FormInstance);
try
{
// Create a new fragment and a transaction.
FragmentTransaction fragmentTx = this.FragmentManager.BeginTransaction();
FrameLayout frame = null;
Widgets.UploadFragment upload = null;
frame = new FrameLayout(this.Context);
frame.LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WrapContent, ViewGroup.LayoutParams.WrapContent);
frame.Id = control.id;
upload = new Widgets.UploadFragment(control, binding, Inflater, a, xFormInstance);
MainFormLayout.AddView(frame);
ControlViews.Add(frame);
fragmentTx.Replace(frame.Id, upload, $"upload_{control.id}");
//fragmentTx.Show(upload);
fragmentTx.Commit();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
This is cleaned code to remove as much irrelevant code as possible. The code shown is the path the code in question moves through.
I found the issue. Part of what I took out of the code above, was the Activity.RunOnUiThread() calls that add the frame to the main view. The issue was caused by Thread Timing. The UI thread was taking so long to add the frame to the view, that when the FragmentTransaction was trying to commit the changes, the frame still did not exist.
How can I assign a drawable png to ImageView in Fragment?
I declare an ImageView in tab's layout:
ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content "
android:layout_rowSpan="2"
android:id ="#+id/iv_icon"
The tab class is here:
class tabFragment1 : V4Fragment
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved)
{
var v = inflater.Inflate(Resource.Layout.tabLayout1, container, false);
return v;
}
}
I want to assign an .png from drawable folder , something like this:
ImageView iv_icon = FindViewById(Resource.Id.iv_icon);
iv_icon.SetImageResource(Resource.Drawable.ic_weather_cloudy_white_48dp);
If I put it in the MainActivity OnCreate() method (below the ViewPager initialisation, of course), I get NullRefenceException Error (on the second row).
The question is: in which class should I put these statements?
Thanks!
In your Fragment OnCreateView once you had inflated the view/layout you can access its ui elements, in your case your ImageView.
Modify this method:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved)
{
var v = inflater.Inflate(Resource.Layout.tabLayout1, container, false);
ImageView iv_icon = v.FindViewById(Resource.Id.iv_icon);
iv_icon.SetImageResource(Resource.Drawable.ic_weather_cloudy_white_48dp);
return v;
}
Hope this helps.-
The problem lies in the instantiation of your ImageView. You can't just call FindViewById, you have to call FindViewById from your fragment reference. Such as:
ImageView iv_icon = fragmentName.FindViewById(Resource.Id.iv_icon);
//fragmentName is the reference to your fragment *after its been inflated*
The exception is being thrown because your trying to call SetImageResource from an ImageView that was instantiated improperly. The ImageView is null and hence a NullReferenceException is being thrown.
On a side note, inflation of a view from an Activity will work as follows:
View v = FindViewById(Resource.Id.X);
FindViewById is a convenience method so that you don't have to keep calling activityName.FindViewById(). This convenience does not exist for fragments.
In summary, inflating a view from an Activity:
View v = FindViewById(Resource.Id.X);
Inflating a view from a fragment:
View v = fragmentName.FindViewById(Resource.Id.X);
In Android, when should (and shouldn't) a view (activity/fragment) be updated with content?
TextView's shouldn't be set with text from the same thread OnCreate() is running on, correct, but instead set on the UI thread (which is done by posting statements to the UI threads MessageQueue)?
95% of my view's set data (in TextViews, TabHosts, Checkboxes, etc... ) directly in OnCreate() (or a function called from it) and works fine, but I realize now that just b/c I've been getting away with it doesn't mean it's right (yes i'm getting bit now).
However, running on the UI thread isn't enough, I'm finding I need to post from OnResume() for 100% guarantee that the RecordView will updated as expected.
The code below show's the two scenarios for calling UpdateView(). Is it enough to call UpdateView() in OnResume() to ensure the RecordView will display with populated data as expected, or is there a better, more correct and/or preferred way of doing this?
Also, is _thisView redundant to _container?
Is the RecordView that is having it's OnCreate() being called the same RecordView that is displayed? Why do I need to inflate the RecordView in OnCreate(), and then return it out? Shouldn't this happen automatically by the runtime, before OnCreate() is called (it's almost like a Factory pattern or something)?
Example Code:
public class RecordView : Fragment
{
private Bundle _bundle;
private ViewGroup _container;
private LayoutInflater _inflater;
ViewGroup _thisView;
TextView _tvTitle, _tvField1, _tvField2;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle)
{
base.OnCreateView(inflater, container, bundle);
_inflater = inflater;
_container = container;
_bundle = bundle;
Render();
return _thisView;
}
public override void Render()
{
_thisView = (ViewGroup) _inflater.Inflate(Resource.Layout.Record, _container, false);
_tvTitle = _thisView.FindViewById<TextView>(Resource.Id.tv_title);
_tvField1 = _thisView.FindViewById<TextView>(Resource.Id.tv_field_1);
_tvField2 = _thisView.FindViewById<TextView>(Resource.Id.tv_field_2);
// *A* SOMETIMES WORKS - Title & fields sometimes blank
UpdateView();
// *B* SOMETIMES WORKS - Title & fields sometimes blank
_thisView.Post(() => { UpdateView(); });
}
public override void OnResume()
{
base.OnResume();
// *C* SOMETIMES WORKS - Title & fields sometimes blank
UpdateView();
// *D* ALWAYS WORKS - Title & fields always display data as expected
_thisView.Post(() => { UpdateView(); });
}
private void UpdateView()
{
_tvTitle.Text = "Todo";
_tvField1.Text = "RTFM";
_tvField2.Text = "ASAP";
}
}
You should show initial data or some loading animation in OnCreateView(..), load data in background and then post actual content on the main thread once it is available, i.E.:
new Handler(Looper.GetMainLooper()).post(() => {
//update views here
});
Also, setting a View's content in OnCreateView(..) ALWAYS works if you use one of the View.set(..) methods, in your case TextView.setText(..).
That is because setters call invalidate() on the view which in turn redraws the view.
It is by design not possible that setting a TextView's text in OnCreateView(..) is not updated on screen.
I FORGOT TO MENTION
No, _thisView is not the same as _container.
_thisView is a direct child of _container.
Consider this:
Your main layout.xml is a FrameLayout.
you add a Fragment to it that has a layout called frag.xml, beeing a TextView.
now the _container is the FrameLayout, but _thisView is the TextView.
I have a layout that I want to show as a popup window (used as a custom options menu) while in a fragment of a viewpager. Therefore, when the "Options" button is clicked, I do the following:
public void onOptionsButtonClicked(int buttonHeight)
{
LayoutInflater optionsLayoutInflater = (LayoutInflater)Context.GetSystemService(Context.LayoutInflaterService);
int popupWidth = ViewGroup.LayoutParams.MatchParent;
int popupHeight = ViewGroup.LayoutParams.WrapContent;
View layout = optionsLayoutInflater.Inflate(Resource.Layout.TrackOptions, null);
int popupYOffset = (85 + buttonHeight) * -1;
var popup = new PopupWindow(context);
popup.Focusable = true;
popup.Width = popupWidth;
popup.Height = popupHeight;
popup.ContentView = layout;
popup.SetBackgroundDrawable(new BitmapDrawable());
popup.OutsideTouchable = true;
popup.ShowAsDropDown(view, 0, popupYOffset);
}
And this works as I want, visually that is. Meaning, I click the button and I do see the layout popup as a popup window with all of my options. HOWEVER, none of the buttons work. I put a breakpoint in the class that should be associated the the layout and noticed that onCreateView never gets called, therefore, none of the buttons and associated click event handlers are ever wired up. So, I know why it is not working. However, I don't know how to fix it. I think it is because, while I inflate the view, I am never actually creating the fragment. I have done fragementmanager transactions to replace a fragment in other parts of my project and I know that would probably do it, however, this is a different case as I am trying to do a popup window.
Thanks!
Mike
Fragment is attach in activity so you can try it in onActivityCreated(Bundle) method
I'm new to Xamarin and new to Android development (this is my first week coming from a windows background). I have an adapter that I'm attempting to add to a listview but I'm not sure where to place my code. I have multiple fragments being inflated from a flyout menu. I attempted placing the code in the fragment itself but this caused compilation errors, I also attempted placing the code in the main activity. Neither seemed to work correctly.
This is the code segment
var items = new String[] { "test", "test2" };
ArrayAdapter<string> adapter = new ArrayAdapter<string> (this, Android.Resource.Layout.SimpleListItem1, items);
var listViewMeds = FindViewById<ListView> (Resource.Id.medicationListView);
listViewMeds.Adapter = adapter;
You will do this in the OnCreateView inside the fragment.
eg
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.MyLayoutId, container, false);
ListAdapter = new MyAdapter(Activity, Items);
return view;
}
Note that your fragment will need to be a ListFragment to be able to access the ListAdapter property.
Alternatively, if you don't have a ListFragment you can find your list view in the OnCreateView, eg.
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.MyLayoutId, container, false);
var myListView = view.FindViewById<ListView>(Resource.Id.MyListViewId);
myListView.ListAdapter = new MyAdapter(Activity, Items);
}