Add fragment to programmatically generated FrameLayout - c#

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.

Related

OnCreateView() happening before OnCreate() completed causing

I have a an Android fragment which essentially displays a custom ListView. My Fragment looks like this:
public class ResultSummaryFragment : Android.Support.V4.App.Fragment
{
private List<ResultSummary> data;
public override async void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
ISharedPreferences preferences = Application.Context.GetSharedPreferences("UserInfo", FileCreationMode.Private);
string id = preferences.GetString("ID", string.Empty);
if (id != null)
{
RunnerData RunnerData = new RunnerData(id);
// This is not happening before OnCreateView?
data = await RunnerData.GetAllResults();
}
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
Context context = Application.Context;
View view = inflater.Inflate(Resource.Layout.results_summary_view, container, false);
ListView listview = view.FindViewById<ListView>(Resource.Id.AllResultsListView);
//data is being passed in as null...
ResultsSummaryListAdapter adapter = new ResultsSummaryListAdapter(context, data);
listview.Adapter = adapter;
return view;
}
}
The problem is that data being passed into ResultsSummaryListAdapter is null because the OnCreateView() code seems to get executed before data = await RunnerData.GetAllResults(); returns. I'm absolutely sure that this does return data (it can take time due to download time).
What am I doing wrong here?
I feel like a better way to handle this would be to instantiate the fragment, start getting the data and then trigger the below code:
ResultsSummaryListAdapter adapter = new ResultsSummaryListAdapter(context, data);
listview.Adapter = adapter;
I'm not sure how to go about that in Android.
Please refer to the below links on how to use Fragments to achieve your requirements.
https://learn.microsoft.com/en-us/xamarin/android/platform/fragments/implementing-with-fragments/
The below link gives you an overview of fragments and is well explained.
https://learn.microsoft.com/en-us/xamarin/android/platform/fragments/
Also, the attached image will explain to you the life cycle of fragments.

Android Xamarin - How To Add Animations To View's Position Changes?

So I'm a bit of a newb in Android. I've read a few tutorials but found it extensively complicated to understand. I was hoping someone can help me understand on how to implement Animations to views on the current context I'm working on (noob friendly).
Let's say this is my .axml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/top_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:orientation="vertical" />
Now this is my activity (Not exactly but enough information to know what I want to do:
private LinearLayout _topContainer;
private IDictionary<string, FrameLayout> _framelayoutViewsDictionary = new Dictionary<string, FrameLayout>();
private LinearLayout.LayoutParams layoutParams;
protected override void OnCreate(Bundle bundle)
{
//SetContentView and other stuff...
layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent, 1);
_topContainer = FindViewById<LinearLayout>(Resource.Id.top_container);
}
public void CreateFirstVideoPlayer
{
_framelayoutViewsDictionary.Add(Constants.Views.TOP_FRAMELAYOUT, new FrameLayout(this)
{
Id = 1,
LayoutParameters = layoutParams
});
var fragmentManager = FragmentManager.BeginTransaction();
fragmentManager.Add(_framelayoutViewsDictionary[Constants.Views.TOP_FRAMELAYOUT].Id, /*Create New MediaPlayer Here*/, Constants.FragmentTag.TOP_FRAGMENT);
fragmentManager.Commit();
_topContainer.AddView(_framelayoutViewsDictionary[Constants.Views.TOP_FRAMELAYOUT]);
}
public void CreateSecondVideoPlayer
{
_framelayoutViewsDictionary.Add(Constants.Views.BOTTOM_FRAMELAYOUT, new FrameLayout(this)
{
Id = 2,
LayoutParameters = layoutParams
});
var fragmentManager = FragmentManager.BeginTransaction();
fragmentManager.Add(_framelayoutViewsDictionary[Constants.Views.BOTTOM_FRAMELAYOUT].Id, /*Create New MediaPlayer Here*/, Constants.FragmentTag.BOTTOM_FRAGMENT);
fragmentManager.Commit();
_topContainer.AddView(_framelayoutViewsDictionary[Constants.Views.BOTTOM_FRAMELAYOUT]);
}
As you can see. I'm programmatically creating a new FrameLayout, adding a fragment on top. And then putting that FrameLayout on top view which is a LinearLayout. Now when I add the second FrameLayout. The first FrameLayout is halved and the second one is added. So now each FrameLayout is taking 50:50 space. So what I want to do is. When the second FrameLayout is added, I want the first FrameLayout to animate so it slowly moves upwards to the top half of the screen instead of instantly appearing on the top. And when I remove the second FrameLayout the first FrameLayout will slowly animate back to the centre of the screen.
Hope this all makes sense. If you need more info please comment!
Using the Support Libraries to perform a fragment slide in/out is as easy as setting the Fragment's custom animations to the built-in resource abc_slide_in_bottom and abc_slide_out_bottom animations.
Example:
SupportFragmentManager
.BeginTransaction()
.SetCustomAnimations(
Resource.Animation.abc_slide_in_bottom,
Resource.Animation.abc_slide_out_bottom,
Resource.Animation.abc_slide_in_bottom,
Resource.Animation.abc_slide_out_bottom)
.AddToBackStack(count.ToString())
.Add(Resource.Id.linearLayout1, new AFragmentSubClass(), count.ToString())
.Commit();

Xamarin, assign image to ImageView in Fragment

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);

Best practice for updating a view with data/content?

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.

Display back button in fragment with Toolbar

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.

Categories