so I have an activity that has a tabbed page with 2 fragments.
public class RecipeDetailActivity : BaseFragmentActivity<RecipeDetailViewModel>
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.RecipeDetailView);
AttachActionBar();
SupportActionBar.SetDisplayHomeAsUpEnabled(true);
SupportActionBar.Title = "Recipe details";
var viewPager = FindViewById<ViewPager>(Resource.Id.main_view_pager);
if (viewPager != null)
{
var fragments = new List<MvxViewPagerFragmentInfo>();
fragments.Add(
new MvxViewPagerFragmentInfo("Ingrediente", typeof(RecipeFlavoursFragment), typeof(RecipeFlavoursViewModel)));
fragments.Add(
new MvxViewPagerFragmentInfo("Flavours", typeof(RecipeIngridientsFragment), typeof(RecipeIngridientsViewModel)));
viewPager.Adapter = new MvxFragmentPagerAdapter(this, SupportFragmentManager, fragments);
viewPager.Adapter = new MvxFragmentPagerAdapter(this, SupportFragmentManager, fragments);
var tabLayout = FindViewById<TabLayout>(Resource.Id.main_tablayout);
tabLayout.SetupWithViewPager(viewPager);
}
}
}
I show this page using the following code.
private void SelectRecipe(RecipeModel recipe)
{
var recipeJson = JsonConvert.SerializeObject(recipe);
ShowViewModel<RecipeDetailViewModel>(new { recipe = recipeJson });
}
Now what I would like is to pass some data to child view models. RecipeFlavoursViewModel RecipeIngridientsViewModel
I've tried so far : Using parameterValueObject
fragments.Add(
new MvxViewPagerFragmentInfo("Ingrediente", typeof(RecipeFlavoursFragment), typeof(RecipeFlavoursViewModel), new { recipe = ViewModel.Recipe }));
Using IMvxBundle
In RecipeDetailViewModel
protected override void SaveStateToBundle(IMvxBundle bundle)
{
bundle.Data["Recipe"] = JsonConvert.SerializeObject(Recipe);
base.SaveStateToBundle(bundle);
}
In RecipeIngridientsViewModel
protected override void InitFromBundle(IMvxBundle parameters)
{
base.InitFromBundle(parameters);
if (parameters.Data.Count != 0)
{
Recipe = JsonConvert.DeserializeObject<RecipeModel>(parameters.Data["recipe"]);
}
}
None of them have worked so far. Any ideas what am I doing wrong? Do I have to use the navigation service from MvvmCross 5 to be able to use InitFromBundle and SaveStateToBundle.
InitFromBundle it's called everytime my fragments is displayed, but SaveStateToBundle from RecipeDetailViewModel never gets called.
In order to do this, you could take advantage of the MvxViewPagerFragmentPresentationAttribute so that the Presenter
takes the responsibility to show the fragments and you just show the ViewModels passing the Recipe
parameter as any other but it has some minor bugs for the moment.
However one way to solve this is to have in your RecipeDetailViewModel
properties with the fragments' ViewModels you want to have in your ViewPager
and load them in the Initialize
so then you can reference them from your RecipeDetailActivity
:
Using Mvx 5 you can use new Navigation to show the ViewModels. If the details are openned from RecipeListViewModel
then:
public class RecipeDetailViewModelArgs
{
public RecipeDetailViewModelArgs(RecipeModel recipe)
{
this.Recipe = recipe;
}
public RecipeModel Recipe { get; }
}
public class RecipeListViewModel : MvxViewModel
{
private readonly IMvxNavigationService navigationService;
public RecipeListViewModel(IMvxNavigationService navigationService)
{
this.navigationService = navigationService;
}
private async Task SelectRecipe(RecipeModel recipe)
{
await this.navigationService.Navigate<RecipeDetailViewModel, RecipeDetailViewModelArgs>(new RecipeDetailViewModelArgs(recipe));
}
}
Then in your details ViewModel you just cache the recipe, load the children ViewModels (ingredients and flavours) and set the recipe to them:
public class RecipeDetailViewModel : MvxViewModel<RecipeDetailViewModelArgs>
{
private readonly IMvxViewModelLoader mvxViewModelLoader;
private readonly IMvxJsonConverter jsonConverter;
private RecipeModel recipe;
public RecipeDetailViewModel(IMvxViewModelLoader mvxViewModelLoader, IMvxJsonConverter jsonConverter)
{
this.mvxViewModelLoader = mvxViewModelLoader;
this.jsonConverter = jsonConverter;
}
public override void Prepare(RecipeDetailViewModelArgs parameter)
{
this.recipe = parameter.Recipe;
}
protected override void SaveStateToBundle(IMvxBundle bundle)
{
base.SaveStateToBundle(bundle);
bundle.Data["RecipeKey"] = this.jsonConverter.SerializeObject(this.recipe);
}
protected override void ReloadFromBundle(IMvxBundle state)
{
base.ReloadFromBundle(state);
this.recipe = this.jsonConverter.DeserializeObject<RecipeModel>(state.Data["RecipeKey"]);
}
public override async Task Initialize()
{
await base.Initialize();
this.InitializeChildrenViewModels();
}
public RecipeFlavoursViewModel FlavoursViewModel { get; private set; }
public RecipeIngridientsViewModel IngredientsViewModel { get; private set; }
protected virtual void InitializeChildrenViewModels()
{
// Load each childre ViewModel and set the recipe
this.FlavoursViewModel = this.mvxViewModelLoader.LoadViewModel(new MvxViewModelRequest<RecipeFlavoursViewModel>(null, null), null);
this.FlavoursViewModel.Recipe = this.recipe;
this.IngredientsViewModel = this.mvxViewModelLoader.LoadViewModel(new MvxViewModelRequest<RecipeIngridientsViewModel>(null, null), null);
this.FlavoursViewModel.Recipe = this.recipe;
}
}
Then when you load the ViewPager
you can take advantage from the other constructor of MvxViewPagerFragmentInfo
=> public MvxViewPagerFragmentInfo (string title, string tag, Type fragmentType, IMvxViewModel viewModel, object parameterValuesObject = null)
so you can pass the ViewModels previously loaded:
this.viewPager = view.FindViewById<ViewPager>(Resource.Id.viewPagerDetails);
if (viewPager != null)
{
var fragments = new List<MvxViewPagerFragmentInfo>();
fragments.Add(new MvxViewPagerFragmentInfo("Ingredients", "RecipeIngridientsViewModelTag", typeof(RecipeIngridientsView), this.ViewModel.IngridientsViewModel));
fragments.Add(new MvxViewPagerFragmentInfo("Flavours", "RecipeFlavoursViewModelTag", typeof(RecipeFlavoursView), this.ViewModel.FlavoursViewModel));
this.viewPager.Adapter = new MvxFragmentPagerAdapter(this.Activity, this.ChildFragmentManager, fragments);
}
That's it.
BTW if you don't want to use Navigation or you are not using Mvx 5.x then you just Initialize the children ViewModels in the void Start()
method.
And to conclude if you want to change values of your Recipe
from the children one simple way is to have a Singleton initialized with your Recipe
and then you just Inject the singleton in the constructors so you always have the reference to the same Recipe and you don't have to pass the Recipe object forth & back to those ViewModels and merge the changes made from each of them. More info in MvvmCross: Accessing models by reference from everywhere
HIH
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With