How to share ShowViewModel class in ViewModel - xamarin

I am developing a Xamarin mobile app using MVVM Cross. There are two ViewModels which are doing same thing i.e. showing a dialog using the code below:
var register = await UserDialogHelper.RaiseNotRegisteredAsync (UserDialogs);
if (register) {
ShowViewModel<WebViewModel> (new
{
url = Urls.RegisterPage,
title = "Register",
});
}
I tried moving this code to static class but unable to resolve ShowViewModel. Can anyone suggest how to resolve ShowViewModel in non-viewmodel class?

When you have methods or properties to share between the same viewmoels. You can just implement a base viewmodel. And the other ones just inherit from this base viewmodel. Like following example:
public abstract class MyBaseViewModel : MvxViewModel
{
public void MyMethod()
{
// Your code
var register = await UserDialogHelper.RaiseNotRegisteredAsync (UserDialogs);
if (register) {
ShowViewModel<WebViewModel> (new
{
url = Urls.RegisterPage,
title = "Register",
});
}
}
}
And then your viewmodels look like this:
public class MyFirstViewModel : MyBaseViewModel
{
}
Inside this MyFirstViewModel you can call the base method MyMethod. And so on...
Edit
If you want to navigate from outside a view/viewmodel: Look at this answer from Stuart or the answer here from #SergioZgz

ShowViewModel comes from MvxNavigatingObject if your class doesn't inherited it you cannot use it.
You could something like this in a class than is not a MvxViewModel:
var viewDispatcher = Mvx.Resolve<IMvxViewDispatcher>();
viewDispatcher.ShowViewModel(new MvxViewModelRequest(
vmtype,
parameterBundle,
presentationBundle,
requestedBy));
But I think that answer from Joehl is the correcty way :)

Related

How to create single observable value?

I've got a top-level Controller object that is holding a reference to three objects (MyObject). I'd like to position these precisely on the page at any time, but I would like each object to also be editable, and I'm not really sure how to do that.
So far, I've got a class that extends ItemFragment and displays my individual items, like this:
class MyObjectFragment(o: MyObject) : ItemFragment<MyObject>() {
override val root = hbox {
...
}
}
Meanwhile, I have a top-level View with a reference to my controller, like this:
class TopLevelView : View() {
val controller = TopLevelController()
override val root = hbox {
add(MyObjectFragment(controller.myObject1))
...
add(MyObjectFragment(controller.myObject2))
...
add(MyObjectFragment(controller.myObject3))
}
}
And right now, all I have for the top level controller is this:
class TopLevelController() : Controller() {
val myObject1 = MyObject()
val myObject2 = MyObject()
val myObject3 = MyObject()
}
I'm trying to figure out what I need to do to wrap these objects as observable values. My first attempt was to add something like this to the init method of MyObjectFragment:
class MyObjectFragment(o: MyObject) : ItemFragment<MyObject>() {
init {
itemProperty.bind(o)
}
...
}
However, that method only takes an ObservableValue<MyObject>. What is the best way to get that to tie all of this together?
You can create an observable list of your objects like so:
class TopLevelController() : Controller() {
val myObjects = FXCollections.observableArrayList<MyObject>(MyObject(), MyObject(), MyObject())
}
Then in your TopLevelView, you can bind this list to a layout node's children property, and inflate the proper Fragment for each object:
class TopLevelView : View() {
val controller = TopLevelController()
override val root = hbox {
bindChildren(controller.myObjects) {
MyObjectFragment(it).root
}
}
}
I'll admit your requirements seem a little vague to me without more information. It'd be helpful to know more of what you want from the item fragments. Are they going to be in a list view or table? Or something more dynamic? And how exactly would they be editing? Would there be a save button or would you expect any input to commit those changes immediately?
import tornadofx.*
class MyObject() {
//val someProperty = SimpleObjectProperty<Something>()
//var some by someProperty
}
class MyObjectModel(myObject: MyObject? = null) : ItemViewModel<MyObject>(myObject) {
//val someBinding = bind(MyObject::someProperty)
}
class MyObjectFragment : ItemFragment<MyObject>() {
val model: MyObjectModel by inject()
override val root = hbox {
label("This is an MyObject Fragment")
//you would bind some control to the model binding in here. For example:
//textfield(model.someBinding)
}
init {
itemProperty.value = model.item
model.bindTo(this)
}
}
class TopLevelController : Controller() {
val myObject1 = MyObject()
val myObject2 = MyObject()
val myObject3 = MyObject()
}
class TopLevelView : View() {
val controller: TopLevelController by inject()
override val root = vbox {
add(setUpObjectFragment(controller.myObject1))
add(setUpObjectFragment(controller.myObject2))
add(setUpObjectFragment(controller.myObject3))
}
fun setUpObjectFragment(obj: MyObject) = find<MyObjectFragment>(Scope(MyObjectModel(obj)))
}
It also seems you're new and are missing a lot of key concepts to utilize from TornadoFX. Using find for instance, is really important for Fragments and Views so they have the proper life cycle.
Then there's Scopes which help with being able to call certain Components with injection.
And finally, there are Model classes, most importantly ItemViewModel which gives you the most functionality with editing, like being able to rollback and commit changes as well as to mark properties as required and add validate filters.
If this isn't a satisfactory solution, please give us more information on what you want, as I might be able to provide a more elegant and concise solution. If these concepts are confusing, please look at Edvin's guide.

Dynamic ViewModel navigation

I am trying to find a way to be able to set from the View to what ViewModel I have to navigate. This is to be able to change the navigation flow without changing the core project.
I thought the easier way would be creating an interface, setting the target ViewModel there and injecting the interface into the ViewModel to then perform the navigation.
public interface IModelMapping
{
MvxViewModel ViewModelToNavigate();
}
public class MyViewModel : MvxViewModel
{
readonly IMvxNavigationService navigationService;
readonly IModelMapping modelMapping;
public MyViewModel(IMvxNavigationService navigationService, IModelMapping modelMapping)
{
this.navigationService = navigationService;
this.modelMapping = modelMapping;
}
public IMvxAsyncCommand GoContent
{
get
{
IMvxViewModel vm = modelMapping.ViewModelToNavigate();
IMvxAsyncCommand navigateCommand = new MvxAsyncCommand(() => navigationService.Navigate<vm>());
return navigteCommand;
}
}
}
The problem with this code is I am getting an error setting the navigationService.Navigate(). The error is 'vm is a variable but it is used like a type'
What about using the URI navigation together with the facade? See also https://www.mvvmcross.com/documentation/fundamentals/navigation#uri-navigation
Say you are building a task app and depending on the type of task you want to show a different view. This is where NavigationFacades come in handy (there is only so much regular expressions can do for you).
mvx://task/?id=00 <– this task is done, show read-only view (ViewModelA)
mvx://task/?id=01 <– this task isn’t, go straight to edit view (ViewModelB)
[assembly: MvxRouting(typeof(SimpleNavigationFacade), #"mvx://task/\?id=(?<id>[A-Z0-9]{32})$")]
namespace *.NavigationFacades
{
public class SimpleNavigationFacade
: IMvxNavigationFacade
{
public Task<MvxViewModelRequest> BuildViewModelRequest(string url,
IDictionary<string, string> currentParameters, MvxRequestedBy requestedBy)
{
// you can load data from a database etc.
// try not to do a lot of work here, as the user is waiting for the UI to do something ;)
var viewModelType = currentParameters["id"] == Guid.Empty.ToString("N") ? typeof(ViewModelA) : typeof(ViewModelB);
return Task.FromResult(new MvxViewModelRequest(viewModelType, new MvxBundle(), null, requestedBy));
}
}
}

Page Navigation using MVVM pattern without using existing MVVM frameworks

I am trying to implement MVVM pattern in my xamarin mobile project.
I have following files for MVVM
LoginView
LoginViewModel
BaseViewModel
Following is my LoginViewModel
public class LoginViewModel : BaseViewModel
{
private bool isLoginIndicator= false;
private string etUserName;
private string etPassword;
public LoginViewModel()
{
OnLogin = new Command(doLogin , ()=>!LoginIndicator);
MessagingCenter.Subscribe<IMessage, EventType>(this, RestApi.UI_EVENT, (sender, eventType) =>
{
LoginIndicator = false;
if (eventType.status)
{
Application.Current.MainPage.DisplayAlert(AppResources.success, "Login done", "Ok");
}
else
{
Application.Current.MainPage.DisplayAlert(AppResources.failed, eventType.errorMessage, "Ok");
}
});
}
public bool LoginIndicator
{
get { return isLoginIndicator; }
set
{
isLoginIndicator = value;
OnPropertyChanged("LoginIndicator");
OnLogin.ChangeCanExecute();
}
}
public string UserName
{
get { return etUserName; }
set
{
etUserName = value;
OnPropertyChanged("UserName");
}
}
public string Password
{
get { return etPassword; }
set
{
etPassword = value;
OnPropertyChanged("Password");
}
}
public Command OnLogin { get; }
void doLogin()
{
LoginIndicator = true;
UserRequest user = new UserRequest();
user.userName = etUserName;
user.password = etPassword;
user.companyId = "CEE";
user.appVersion = Constants.getAppVersion();
user.osVersion = Constants.getOSVersion();
user.deviceId = Constants.getDeviceModel() + " " + Constants.getDevicePlatform();
new RestApi().userLogin(JsonConvert.SerializeObject(user));
}
}
This class usually makes a webservice call when OnLogin command gets fired from Button and broadcast the Message using MessageCenter
Now i want to navigate to my MainPage which is master page once the user is logged in successfully hence i need to navigate to master page when eventType.status is true inside the Message Subscriber
but i don't know how can i properly navigate to other pages according to MVVM pattern.
i tried to search on net and i found there are ready made frameworks available like MVVMCross and MVVMLight etc. But i do not want to use those dependecies and willing to implement navigation some other way if anyone can suggest
MVVM says nothing about navigation, so basically every option will be fine.
The only thing against code like:
Application.Current.MainPage = new MyFirstPageAfterLogin();
Is that you now have a reference to a page from your ViewModel, which should not be what you want. That is why MVVM frameworks tend to implement a concept called ViewModel-to-ViewModelnavigation. With that, you can specify a ViewModel that you want to navigate to. Depending on the framework (or how they implemented it), they have you register a coupling first or use a naming convention. For instance; I like to use FreshMvvm, which does this by naming convention.
So when I want to navigate to the PageAfterLoginPage, I create a PageAfterLoginPageModel. From my ViewModel (or PageModel in Xamarin naming) I can now navigate to the PageModel, instead of making a hard reference to the page. This way, Page and PageModel are separated and I can easily swap out the View if I wanted to.
So, either use an already existing framework, or peek into their Github repo to see how they do it if you insist on doing it yourself.
With the latest tools do a File / New Project / CrossPlatform / Master-Detail. The master-detail template is all MVVM, without using any 3rd party frameworks. There are permutatations of native and forms. Great for learning and exploring.
Healy in Tampa.

Binding CalendarView DateChange event with MvvmCross

I have a CalendarView that looks like this:
<CalendarView
android:layout_width="match_parent"
android:layout_height="300dp"
android:id="#+id/createReservationCalendarView" />
Here is how I handle the DateChange event without MvvmCross:
protected override void OnCreate(Bundle savedInstanceState)
{
... Code ...
calendar.DateChange += (s, args) =>
{
var year = args.Year;
var month = args.Month + 1;
var dayOfMont = args.DayOfMonth;
var date = new DateTime(year, month, dayOfMont);
var myReservations = new Intent(this, typeof(CreateReservationTimeslotScreen));
myReservations.PutExtra("selectedDate", date.Ticks);
StartActivity(myReservations);
};
}
Now that I have switched to MvvmCross, I would like to have my ViewModel start the new activity instead.
Im not sure how to do this, since the ViewModel should be OS and UI agnostic.
The "args" argument is of type CalendarView.DateChangeEventArgs, which is Android specific, so I cant use that in the ViewModel. It derives from System.EventArgs, so maybe I could use that instead. I am thinking that there must be a simpler way.
A thought that I had was if it is possible to update a property on the ViewModel from the activity, and then execute the switch to the new Activity from there? I'm not sure how this could be accomplished since activites dont have references to their ViewModels.
Any suggestions?
Thanks.
MvvmCross does give you access to your ViewModel from your View. The relationship between your View (e.g. Activity/fragment in Android) and your ViewModel, and their ability to share data (models) in both directions is a core characteristic a Mvvm framework.
In order to setup an Activity to be used with MvvmCross you need to make sure to inherit from MvxActivity or MvxAppCompatActivity (If using Android Support Library). Following which you need to link your Activity to its corresponding ViewModel using one of the possible conventions (See link, for basic sample of each registration offered by the MvxViewModelViewTypeFinder). A simple example would be to use the concrete type based registration using the type parameter overload.
public class FirstActivity : MvxAppCompatActivity<FirstViewModel>
Now that you have access to your ViewModel from your View you can create a command that can be used to execute the navigation:
CalendarViewModel (ViewModel linked to the current Activity in question)
Create a command that requires a DateTime parameter, which in turn will pass the value when navigation (see MvvmCross Navigation docs for alternative navigation and parameter passing conventions).
public class CalendarViewModel : MvxViewModel
{
IMvxCommand _goToMyReservationCommand;
public IMvxCommand GoToMyReservationCommand =>
_goToMyReservationCommand ??
(_goToMyReservationCommand = new MvxCommand<DateTime>(NavigateToMyReservation));
void NavigateToMyReservation(DateTime reservationDate)
{
ShowViewModel<MyReservationViewModel>(
new GoToMyReservationParameter
{
ReservationTicks = reservationDate.Ticks
});
}
}
Navigation Parameter Class
Holds the values and type information used for navigation.
public class GoToMyReservationParameter
{
public long ReservationTicks { get; set; }
}
MyReservationViewModel
The ViewModel that will receive the value passed.
public class MyReservationViewModel : MvxViewModel
{
public void Init(GoToMyReservationParameter parameters)
{
var reservationTicks = parameters.ReservationTicks;
// Do what you need with the parameters
}
}
View
Execute the command on the ViewModel and pass through the DateTime object.
public class CalendarActivity : MvxAppCompatActivity<CalendarViewModel>
{
protected override void OnCreate(Bundle bundle)
{
... Code...
calendar.DateChange += (s, args) =>
{
var year = args.Year;
var month = args.Month + 1;
var dayOfMont = args.DayOfMonth;
var date = new DateTime(year, month, dayOfMont);
ViewModel.GoToMyReservationCommand.Execute(date);
};
}
}

Import does not resolve needed type in asp.net mvc 3 with mef

I used an example on extensible asp.net mvc 3 to build my plug-able application, but I encountered a problem. In a plug-in I declared and implmemented an interface.
But, in plug-in controller when I want to use this class, the application throws an error and it seems that EntityConfig was not initialized. How can this be fixed?
[Export(typeof(IController)), ExportMetadata("controllerName", "Concept")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ConceptController : Controller
{
[Import(typeof(IEntityConfig))]
private IEntityConfig EntityConfig;
public ActionResult Index()
{
var obs = EntityConfig.EntityName;
return View("~/Bin/Views/Concept/Index.cshtml",obs );
}
}
public interface IEntityConfig
{
string EntityName { get;}
}
[Export(typeof(IEntityConfig))]
public class TestEntity : IEntityConfig
{
public string EntityName
{
get{return "Test";}
}
}
Edited :
In other side, when I is use this example, there is no problem in resolving EntityConfig, but in the view, when I want to load model as follows :
#using Concepts
#model Concepts.Models.TestModel
the application throws an error and tells me 'The type or namespace name 'Concepts' could not be found', although when I check container after it was initiated, I can see Concepts in in loaded assemblies.
Would you please help me ?
Thanks.
Edited :
I uploaded the samples :
First one
Second one
Edited (2011/22/09):
I tested the above code on other sample that #Matthew Abbott provided in his blog, and it worked, although this sample has been built against mvc 2.0.
Looking over your code, can you be sure that the part is actually being imported? Your constructor code for your composition container is such like:
var discoverableControllerFactory = new DiscoverableControllerFactory(
new CompositionContainer(
new DirectoryCatalog(extensionsPath))
);
You're only including your extensions path as a catalog. Can you guaruntee that you're also including your base application path, e.g.:
var discoverableControllerFactory = new DiscoverableControllerFactory(
new CompositionContainer(
new AggregateCatalog(
new DirectoryCatalog("bin"),
new DirectoryCatalog(extensionsPath)))
);
If the parts actually exist in your Unity container, you could add an export provider that grabs those parts from that container and allows them to be composed by MEF.
As for your second problem, you will need to subclass the System.Web.WebPages.Razor.RazorBuildProvider to ensure it includes assemblies in your extensions directory:
namespace ExtensibleMvcApplication
{
public class CustomRazorBuildProvider : RazorBuildProvider
{
public static IEnumerable<Assembly> _assemblies;
static CustomRazorBuildProvider()
{
string extensionsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Extensions");
_assemblies = Directory.GetFiles(extensionsPath, "*.dll")
.Select(Assembly.Load);
}
public override void GenerateCode(System.Web.Compilation.AssemblyBuilder assemblyBuilder)
{
foreach (var assembly in _assemblies)
assemblyBuilder.AddAssemblyReference(assembly);
base.GenerateCode(assemblyBuilder);
}
}
}
Which you'd need to register in your config:
<buildProviders>
<remove extension=".cshtml" />
<add extension=".cshtml" type="ExtensibleMvcApplication.CustomRazorBuildProvider, ExtensibleMvcApplication"/>
</buildProviders>

Resources