এই থ্রেডটি খুব জনপ্রিয় বলে মনে হচ্ছে এবং এখানে বিকল্প উপায় আছে তা উল্লেখ না করে দুঃখ হবে - ViewModel First Navigation
। বেশিরভাগ এমভিভিএম ফ্রেমওয়ার্কগুলি এটি ব্যবহার করে বাইরে চলে যায়, তবে আপনি যদি এটি সম্পর্কে কী বুঝতে চান তবে পড়া চালিয়ে যান।
সমস্ত সরকারী Xamarin. forms ডকুমেন্টেশন একটি সাধারণ, এখনও সামান্য এমভিভিএম খাঁটি সমাধান প্রদর্শন করে না। কারণ Page
(দেখুন) এর ViewModel
বিপরীতে এবং কিছুই সম্পর্কে কিছু জানা উচিত নয় । এই লঙ্ঘনের একটি দুর্দান্ত উদাহরণ এখানে:
// C# version
public partial class MyPage : ContentPage
{
public MyPage()
{
InitializeComponent();
// Violation
this.BindingContext = new MyViewModel();
}
}
// XAML version
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
x:Class="MyApp.Views.MyPage">
<ContentPage.BindingContext>
<!-- Violation -->
<viewmodels:MyViewModel />
</ContentPage.BindingContext>
</ContentPage>
আপনার যদি 2 পৃষ্ঠার অ্যাপ্লিকেশন থাকে তবে এই পদ্ধতিটি আপনার পক্ষে ভাল। তবে আপনি যদি একটি বড় উদ্যোগ সমাধানে কাজ করে থাকেন তবে আপনি আরও ভালভাবে একটি ViewModel First Navigation
পদ্ধতির সাথে যেতে পারেন । এটি কিছুটা জটিল তবে অনেক ক্লিনার অ্যাপ্রোচ যা আপনাকে (দর্শন) এর ViewModels
মধ্যে নেভিগেশনের পরিবর্তে নেভিগেট করতে দেয় Pages
। উদ্বেগের স্পষ্ট বিচ্ছিন্নতার পাশাপাশি একটি সুবিধা হ'ল আপনি সহজেই পরবর্তীগুলিতে পরামিতিগুলি পাস করতে পারেনViewModel
বা নেভিগেশনের ঠিক পরে একটি অ্যাসিঙ্ক সূচনা কোডটি কার্যকর । এখন বিস্তারিত।
(আমি যতটা সম্ভব কোডের সমস্ত উদাহরণকে সহজ করার চেষ্টা করব)।
১. সবার আগে আমাদের এমন একটি জায়গা প্রয়োজন যেখানে আমরা আমাদের সমস্ত বস্তুর নিবন্ধন করতে পারি এবং lifetimeচ্ছিকভাবে তাদের জীবনকালকে সংজ্ঞায়িত করতে পারি। এই বিষয়ে আমরা একটি আইওসি ধারক ব্যবহার করতে পারি, আপনি নিজেই এটি চয়ন করতে পারেন। এই উদাহরণে আমি অটোফ্যাক ব্যবহার করব (এটি দ্রুত উপলব্ধগুলির মধ্যে একটি)। আমরা এটিতে একটি রেফারেন্স রাখতে পারি App
যাতে এটি বিশ্বব্যাপী উপলব্ধ হবে (একটি ভাল ধারণা নয়, তবে সরলকরণের জন্য প্রয়োজনীয়):
public class DependencyResolver
{
static IContainer container;
public DependencyResolver(params Module[] modules)
{
var builder = new ContainerBuilder();
if (modules != null)
foreach (var module in modules)
builder.RegisterModule(module);
container = builder.Build();
}
public T Resolve<T>() => container.Resolve<T>();
public object Resolve(Type type) => container.Resolve(type);
}
public partial class App : Application
{
public DependencyResolver DependencyResolver { get; }
// Pass here platform specific dependencies
public App(Module platformIocModule)
{
InitializeComponent();
DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
MainPage = new WelcomeView();
}
/* The rest of the code ... */
}
২. আমাদের Page
নির্দিষ্ট ViewModel
এবং তদ্বিপরীত জন্য একটি (দেখুন) পুনরুদ্ধার করার জন্য আমাদের দায়ী কোনও জিনিসের প্রয়োজন হবে । অ্যাপ্লিকেশনটির মূল / প্রধান পৃষ্ঠা সেট করার ক্ষেত্রে দ্বিতীয় কেসটি কার্যকর হতে পারে। তার জন্য আমাদের একটি সাধারণ সম্মেলনে একমত হওয়া উচিত যে সমস্ত ডিরেক্টরিতে ViewModels
থাকতে হবে ViewModels
এবং Pages
(ভিউ) Views
ডিরেক্টরিতে থাকা উচিত । অন্য কথায় নাম স্পেসে এবং নেমস্পেসে (ভিউ) ViewModels
থাকা উচিত । এটি ছাড়াও আমাদের একমত হওয়া উচিত যে (পৃষ্ঠা) এর একটি এবং ইত্যাদি হওয়া উচিত এখানে ম্যাপারের কোড উদাহরণ রয়েছে:[MyApp].ViewModels
Pages
[MyApp].Views
WelcomeView
WelcomeViewModel
public class TypeMapperService
{
public Type MapViewModelToView(Type viewModelType)
{
var viewName = viewModelType.FullName.Replace("Model", string.Empty);
var viewAssemblyName = GetTypeAssemblyName(viewModelType);
var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName);
return Type.GetType(viewTypeName);
}
public Type MapViewToViewModel(Type viewType)
{
var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.");
var viewModelAssemblyName = GetTypeAssemblyName(viewType);
var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName);
return Type.GetType(viewTypeModelName);
}
string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName;
string GenerateTypeName(string format, string typeName, string assemblyName) =>
string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName);
}
৩. মূল পৃষ্ঠা নির্ধারণের ক্ষেত্রে আমাদের এর প্রয়োজন হবে যা স্বয়ংক্রিয়ভাবে ViewModelLocator
সেট হয়ে BindingContext
যাবে:
public static class ViewModelLocator
{
public static readonly BindableProperty AutoWireViewModelProperty =
BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);
public static bool GetAutoWireViewModel(BindableObject bindable) =>
(bool)bindable.GetValue(AutoWireViewModelProperty);
public static void SetAutoWireViewModel(BindableObject bindable, bool value) =>
bindable.SetValue(AutoWireViewModelProperty, value);
static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>();
static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
{
var view = bindable as Element;
var viewType = view.GetType();
var viewModelType = mapper.MapViewToViewModel(viewType);
var viewModel = (Application.Current as App).DependencyResolver.Resolve(viewModelType);
view.BindingContext = viewModel;
}
}
// Usage example
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
viewmodels:ViewModelLocator.AutoWireViewModel="true"
x:Class="MyApp.Views.MyPage">
</ContentPage>
4. শেষ পর্যন্ত আমাদের এমন একটি প্রয়োজন হবে যা পদ্ধতির NavigationService
সমর্থন করবে ViewModel First Navigation
:
public class NavigationService
{
TypeMapperService mapperService { get; }
public NavigationService(TypeMapperService mapperService)
{
this.mapperService = mapperService;
}
protected Page CreatePage(Type viewModelType)
{
Type pageType = mapperService.MapViewModelToView(viewModelType);
if (pageType == null)
{
throw new Exception($"Cannot locate page type for {viewModelType}");
}
return Activator.CreateInstance(pageType) as Page;
}
protected Page GetCurrentPage()
{
var mainPage = Application.Current.MainPage;
if (mainPage is MasterDetailPage)
{
return ((MasterDetailPage)mainPage).Detail;
}
// TabbedPage : MultiPage<Page>
// CarouselPage : MultiPage<ContentPage>
if (mainPage is TabbedPage || mainPage is CarouselPage)
{
return ((MultiPage<Page>)mainPage).CurrentPage;
}
return mainPage;
}
public Task PushAsync(Page page, bool animated = true)
{
var navigationPage = Application.Current.MainPage as NavigationPage;
return navigationPage.PushAsync(page, animated);
}
public Task PopAsync(bool animated = true)
{
var mainPage = Application.Current.MainPage as NavigationPage;
return mainPage.Navigation.PopAsync(animated);
}
public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
InternalPushModalAsync(typeof(TViewModel), animated, parameter);
public Task PopModalAsync(bool animated = true)
{
var mainPage = GetCurrentPage();
if (mainPage != null)
return mainPage.Navigation.PopModalAsync(animated);
throw new Exception("Current page is null.");
}
async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
{
var page = CreatePage(viewModelType);
var currentNavigationPage = GetCurrentPage();
if (currentNavigationPage != null)
{
await currentNavigationPage.Navigation.PushModalAsync(page, animated);
}
else
{
throw new Exception("Current page is null.");
}
await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
}
}
আপনি দেখতে পাচ্ছেন যে সমস্ত BaseViewModel
- ViewModels
সেখানে আপনি যেমন পদ্ধতি নির্ধারণ করতে পারেন তার জন্য অ্যাবস্ট্রাক্ট বেস ক্লাসটি InitializeAsync
নেভিগেশনের ঠিক পরে কার্যকর হবে। এবং এখানে নেভিগেশন উদাহরণ:
public class WelcomeViewModel : BaseViewModel
{
public ICommand NewGameCmd { get; }
public ICommand TopScoreCmd { get; }
public ICommand AboutCmd { get; }
public WelcomeViewModel(INavigationService navigation) : base(navigation)
{
NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>());
TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>());
AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>());
}
}
আপনি যেমন বুঝতে পেরেছেন এই পদ্ধতিটি আরও জটিল, ততই ডিবাগ করা শক্ত এবং বিভ্রান্তিকর হতে পারে। তবে অনেকগুলি সুবিধাসমূহ রয়েছে প্লাস আপনাকে নিজেরাই এটি বাস্তবায়ন করতে হবে না কারণ বেশিরভাগ এমভিভিএম ফ্রেমওয়ার্কগুলি বক্সের বাইরে এটি সমর্থন করে। এখানে প্রদর্শিত কোড উদাহরণটি গিথুবে উপলভ্য । অ্যাপ্রোচ
সম্পর্কে প্রচুর ভাল নিবন্ধ ViewModel First Navigation
রয়েছে এবং এখানে একটি বিনামূল্যে এন্টারপ্রাইজ অ্যাপ্লিকেশন প্যাটার্ন রয়েছে যা জামারিন.ফর্মস ইবুক ব্যবহার করে যা এই এবং আরও অনেক আকর্ষণীয় বিষয় বিস্তারিতভাবে ব্যাখ্যা করছে।