I have a tabbed application that uses MvxTabsFragmentActivity.
When I navigate between tabs, there is the correct behaviour but the first time when I try to navigate inside a fragment, the called fragment is not replaced.
I tried to use both IMvxNavigationService and also the classic method, using ShowViewModel but they both act the same.
I also used a custom presenter for showing the fragments, but with or without it, the problem stills the same.
There is the image with the printscreen
https://imgur.com/a/cfRWlsl
The red highlighted is the fragment where I am navigating to and the black one is the previous fragment.
This is the View that has the button needed for navigation:
public class TrackView : MvxFragment<TrackViewModel>
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.track_layout, null);
var nextButton = view.FindViewById<Button>(Resource.Id.navigate);
var set = this.CreateBindingSet<TrackView, TrackViewModel>();
set.Bind(nextButton).To(vm => vm.NavigateEventCommand);
set.Apply();
return view;
}
}
This is its ViewModel:
public class TrackViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public TrackViewModel(IMvxNavigationService navigationService)
{
this._navigationService = navigationService;
}
public IMvxCommand NavigateEventCommand => new MvxAsyncCommand(NavigateCommand);
private async Task NavigateCommand()
{
await _navigationService.Navigate<RegistrationViewModel>();
}
}
This is the View where I am navigating to:
public class RegistrationView : MvxFragment
{
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
var view = this.BindingInflate(Resource.Layout.registration_layout, null);
//var set = this.CreateBindingSet<RegistrationView, RegistrationViewModel>();
//set.Apply();
return view;
}
}
This is its ViewModel:
public class RegistrationViewModel : MvxViewModel
{
}
I have also a tabbed Application that uses MvxTabsFragmentActivity, and I am creating the tabs like this:
protected override void AddTabs(Bundle args)
{
this.AddTab<TrackView>(
args,
Mvx.IocConstruct<TrackViewModel>(),
this.CreateTabFor("0", Resource.Drawable.ic_track, "Track"));
this.AddTab<SendView>(
args,
new SendViewModel(),
this.CreateTabFor("1", Resource.Drawable.ic_send, "Send"));
this.AddTab<ProfileView>(
args,
new ProfileViewModel(),
this.CreateTabFor("2", Resource.Drawable.ic_profile, "Profile"));
this.AddTab<MoreView>(
args,
new MoreViewModel(),
this.CreateTabFor("3", Resource.Drawable.ic_more, "More"));
}
I have a custom presenter that is registered this way in the main class:
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
this.tabHost = this.FindViewById<TabHost>(Android.Resource.Id.TabHost);
presenter = (DroidPresenter)Mvx.Resolve<IMvxAndroidViewPresenter>();
var initialFragment = new TrackView() { ViewModel = Mvx.IocConstruct<TrackViewModel>() };
presenter.RegisterFragmentManager(FragmentManager, initialFragment);
}
As I said, with or without the presenter, there is the same behaviour.
I am using MvvmCross 5.7.
Related
There is a class inheritor from the handler through which I control the graphic button.
To search for this button, I pass in the designer a link to the activation. But I want to pass the view there. How can I get a view from activity?
public class FlActivity : AppCompatActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.activity_fl);
toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
tabLayout = FindViewById<TabLayout>(Resource.Id.tabLayout);
viewPager = FindViewById<ViewPager>(Resource.Id.viewPager);
fpAdapter = new FpAdapter(SupportFragmentManager, null);
uiHandler = new UiHandler(this, MainLooper);
}
}
public class UiHandler : Handler
{
private Activity activity { get; }
public UiHandler(Activity a, Looper loader) : base(loader)
{
activity = a;
}
public override void HandleMessage(Message msg)
{
activity.FindViewById<ImageButton>(Resource.Id.imageButton1).SetImageResource(msg.Data.GetInt("ID", Resource.Drawable.bt_off));
}
}
If I change private Activity activity { get; } to private View view { get; }, how can I transfer the view from the main activity when creating a handler instance. What to replace this in creating a nadler object?
There are quite several ways to do this :
this.Window.DecorView.RootView;
or
this.Window.DecorView.FindViewById(Android.Resource.Id.Content);
or
this.FindViewById(Android.Resource.Id.Content);
or
this.FindViewById(Android.Resource.Id.Content).RootView;
or
((ViewGroup) this.FindViewById(Android.Resource.Id.Content)).GetChildAt(0);
Although, since it's probably a layout, not a single view, I recommend using ViewGroup instead of View (these methods returns View so you have to cast them to ViewGroup if you want)
=================
Credit to this answer
I am making an android app with Xamarin. In one of my activities I have a frame layout that I put fragments in. In the transaction I use SetCustomAnimations(), Replace(), and AddToBackStack(). When I press the back button or call OnBackPressed(), the app crashes with the exception:
Java.Lang.IllegalStateException: The specified child already has a parent.
You must call removeView() on the child's parent first.
If I remove the call to SetCustomAnimations() it works, but I don't get any animations.
The activity:
[Android.App.Activity(Label = "Login")]
public class LoginActivity : AppCompatActivity
{
private FrameLayout view;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
view = new FrameLayout(this)
{
Id = View.GenerateViewId()
};
SetContentView(view);
SupportFragmentManager.BeginTransaction().Add(view.Id, SelectDomainFragment.Create()).Commit();
}
public void ShowFragment(Fragment fragment)
{
SupportFragmentManager.BeginTransaction()
.SetCustomAnimations(Resource.Animation.enter_right, Resource.Animation.exit_left, Resource.Animation.enter_left, Resource.Animation.exit_right)
.Replace(view.Id, fragment)
.AddToBackStack(null)
.Commit();
}
}
SelectDomainFragment:
public class SelectDomainFragment : Fragment
{
private SelectDomainView view;
public static SelectDomainFragment Create() => new SelectDomainFragment();
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
view = new SelectDomainView(Context);
view.CloudButtonClicked += (sender, e) => ToLogin(1);
view.OnPremButtonClicked += (sender, e) => ToLogin(2);
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) => view;
private void ToLogin(int target) => ((LoginActivity)Activity).ShowFragment(LoginFragment.Create(target));
}
LoginFragment:
public class LoginFragment : Fragment
{
private LoginView view;
public static LoginFragment Create(int target)
{
var args = new Bundle();
args.PutInt("target", target);
return new LoginFragment
{
Arguments = args
};
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
view = new LoginView(Context);
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) => view;
}
I am very new to Xamarin so i don't have the clearest idea on how inflators work.
How can i add functionality to a page that uses inflators to scroll between tabs? I.E. Where do i put the code for a button click event, when the button can be found in
var root = inflater.Inflate(Resource.Layout.Fragment, container, false); ?
This is my fragment page:
namespace OfficialApp{
public class ContentFragment: Fragment{
private int position;
public static ContentFragment NewInstance(int position)
{
var f = new ContentFragment();
var b = new Bundle();
b.PutInt("position", position);
f.Arguments = b;
return f;
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
position = Arguments.GetInt("position");
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var root = inflater.Inflate(Resource.Layout.Fragment, container, false);
var editFile = inflater.Inflate(Resource.Layout.editFile, container, false);
if (position == 0)
{
return root;
}
else if (position == 1)
{
return editFile;
}
else
ViewCompat.SetElevation(root, 50);
return root ;
}
}
}
Sorry if this is a vague or silly question but i honestly have not been able to find the answer anywhere.
What i'm not sure about is how to give each fragment its own activity so that for example pushing a button runs an on click event.
To find a control in the view which is inflated by var root = inflater.Inflate(Resource.Layout.Fragment, container, false);, you can code for example like this:
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
var view = inflater.Inflate(Resource.Layout.fragment1, container, false);
var btn = view.FindViewById<Button>(Resource.Id.btn);
return view;
}
I have two tabs in viewpager. So in fragment1 I input a text and when in tab2 is selected and then fragment2 will get data from that text from fragment1.
ex: when I open tab1 -> I input a text is "abcd" -> I open tab2 -> I want to get a text is "abcd" from tab1(fragment1).
public class Fragment1 : Android.Support.V4.App.Fragment
{
Fragment2 frag2 = new Fragment2();
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.YourFragment, container, false);
Singleton singleton = Singleton.GetInstance();;
singleton.SetSource("abcd");
}
}public class Singleton
{
private static Singleton singleton;
string a = null;
private Singleton()
{
}
public static Singleton GetInstance()
{
if (singleton == null)
singleton = new Singleton();
return singleton;
}
public void SetSource(string text)
{
this.a = text;
}
public string showMessage()
{
return a;
}
}
public class Fragment2 : Android.Support.V4.App.Fragment
{
public List<string> data = new List<string>();
public void add(string t)
{
data.Add(t);
}
TextView txt;
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.YourFragment, container, false);
//return base.OnCreateView(inflater, container, savedInstanceState);
return inflater.Inflate(Resource.Layout.view2, container, false);
txt = View.FindViewById<TextView>(Resource.Id.txtgetdata);
if (data != null)
{
txt.Text = data[0];
}
else
Toast.MakeText(Application.Context, "no", ToastLength.Long).Show();
}
}
I have tried it but don't having the result.
After a few hours of research/test & debugging and solved, try this out.
In your Activity
add public string tabFragment { get; set; }
In your source fragment
ie on click or that navigate to other fragment
string tab = ((ActivityUser)Activity).tabFragmentAddUser;
FragmentAddUser fragment = (FragmentAddUser);
Activity.SupportFragmentManager.FindFragmentByTag(tab);
fragment.setValText("Hello");
viewPager.SetCurrentItem(1, true);
In your destination fragment (ie which elements will be modified)
add a method like this
public void setValText(string str) { txtUsrFirstName.Text = str; }
==================================================================
Fragment fragment = new Fragment();
Bundle bundle = new Bundle();
bundle.putInt(key, value);
fragment.SetArguments(bundle);
in on create of the other fragment
Bundle bundle = this.GetArguments();
int myInt = bundle.GetInt(key, defaultValue);
optionaly two fragments should never communicate directly and should communicate through activity
From Developers website:
Often you will want one Fragment to communicate with another, for
example to change the content based on a user event. All
Fragment-to-Fragment communication is done through the associated
Activity. Two Fragments should never communicate directly.
refer this and this
I don't know why it is not working but what am I doing wrong. It works fine if I explicitly set the TextView and not using Inflate method by the LayoutInflater. It just closes my application when I run it and when I switch to Test tab it'll just crash with no errors in the output window.
public class MyProfileActivity : Activity
{
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
ActionBar actionBar = ActionBar;
actionBar.NavigationMode = ActionBarNavigationMode.Tabs;
actionBar.SetDisplayShowTitleEnabled (false);
ActionBar.Tab tab = actionBar.NewTab ();
tab.SetText ("Home");
// tab.SetIcon (Resource.Drawable.tab_home);
tab.SetTabListener(new TabListener<HomeFragment>(this, "home"));
actionBar.AddTab (tab);
tab = actionBar.NewTab ();
tab.SetText("Terms");
// tab.SetIcon(Resource.Drawable.tab_terms);
tab.SetTabListener (new TabListener<TermsFragment> (this, "terms"));
actionBar.AddTab (tab);
tab = actionBar.NewTab();
tab.SetText("test");
// tab.SetIcon(Resource.Drawable.tab_terms);
tab.SetTabListener(new TabListener<TestFragment>(this, "test"));
actionBar.AddTab(tab);
}
}
Fragments
public class TestFragment : Fragment
{
private View _fragmentView;
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
_fragmentView = inflater.Inflate(Resource.Layout.Tabs, container);
return _fragmentView;
}
}
#region HomeFragment
public class HomeFragment : Fragment
{
public HomeFragment() : base() {}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
TextView tv = new TextView (Activity);
tv.Text = "Home";
return tv;
}
}
#endregion
#region TermsFragment
public class TermsFragment : Fragment
{
public TermsFragment() : base() {}
public override View OnCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
TextView tv = new TextView (Activity);
tv.Text = "Terms";
return tv;
}
}
#endregion
TabListener
public class TabListener<T> : Java.Lang.Object, ActionBar.ITabListener where T : Fragment
{
#region Members
private Fragment m_fragment;
private readonly Activity m_activity;
private readonly string m_tag;
private readonly T m_fragmentType;
#endregion
#region Constructor
public TabListener(Activity activity, String tag)
{
// The Activity is our host Activity and provides the Context.
// The type T and the tag will be used to instantiate the fragment.
// m_fragment will hold the instance for reuse after it is first created.
m_activity = activity;
m_tag = tag;
}
#endregion
#region ITabListener implementation
public void OnTabSelected(ActionBar.Tab tab, FragmentTransaction ft)
{
// Instantiate the reference and add it to the container,
// or attach it if it has already been created
if (m_fragment == null)
{
m_fragment = Fragment.Instantiate(m_activity, Java.Lang.Class.FromType(typeof(T)).Name);
ft.Add(global::Android.Resource.Id.Content, m_fragment, m_tag);
}
else
{
ft.Show(m_fragment);
}
}
public void OnTabUnselected(ActionBar.Tab tab, FragmentTransaction ft)
{
if (m_fragment != null)
{
ft.Hide(m_fragment);
}
}
public void OnTabReselected(ActionBar.Tab tab, FragmentTransaction ft)
{
// User selected the already selected tab. Usually do nothing.
}
#endregion
public class TestFragment : Fragment
{
private View _fragmentView;
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Create your fragment here
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
_fragmentView = inflater.Inflate(Resource.Layout.Tabs, container);
return _fragmentView;
}
}
}
I tried using with your code and got java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. when navigating to TestFragment.
This answer allowed me to fix the problem by providing false for parameter attachToRoot:
_fragmentView = inflater.Inflate(Resource.Layout.TestLayout, container, false);