V
V
VitCher2016-05-12 16:28:08
Android
VitCher, 2016-05-12 16:28:08

Migration to MVVMcross, how to do it right?

Good afternoon, we are developing an iMonitoring application on a self-made MVVM architecture.
In the general core, we have a model class ViewM, a model controller ViewControllerMC which has methods and events. The ViewControllerVC view controller subscribes to these events, in which work with the UI of each of the platforms is already taking place.
Those. the schema is ViewM <-> ViewControllerMC <---> ViewControllerVC.
ViewControllerVC on iOS is derived from UIViewController, while on Android it is a separate ViewControllerVC class.
And the issue of migration lies precisely in it.
The main idea is that the ViewControllerVC, as it were, corresponds to UIViewControllerVC and can include child ViewControllerVCs. Android Activity and fragments act as containers into which the root ViewControllerVCs are inserted (more precisely, the views controlled by them are Android Views). Each ViewControllerVC works with a specific model and has its own View (RelativeLayout, FrameLayout, LinearLayout) with which it fumbles, listening to signals from the model.
I present this class below.

public class ViewControllerVC : IViewAndroidController
    {
        private ViewControllerMC modelController;

        private NavigationItemVC navigationItemVC;

        private Activity activity;

        public ViewControllerVC(Activity activity)
        {
            IsDestroyed = false;
            this.activity = activity;
        }

        public ViewControllerVC(Activity activity, ViewControllerMC model, View view = null)
        {
            IsDestroyed = false;
            this.activity = activity;

            if (view == null)
            {
                // есть базовый алгоритм загрузки View, но его можно перекрыть
                view = LoadBaseView(model);
            }

            if (view != null)
            {
                SetControlledView(view, model.View);
                BindWithModel(model);
            }
        }

        public event EventHandler ViewWillDestroy;

        /// <summary>
        /// Если NavigationController == null то операции Push, Present реализуют запуск ВьюКонтроллера в новом Активити (ContentActivity.cs),
        /// если не null то обращаются к NavigationController чтобы он открыл вьюху и вьюКонтрллер в 
        /// своем окне поверх текщуего вьюКонтроллера через AddView(...).
        /// </summary>
        /// <value>Навгационный контроллер</value>
        public NavigationVC NavigationController { get; set; }

        public string ModelUUID { get; private set; }

        public Activity Activity
        { 
            get
            {
                return activity;
            }
        }

        public ViewVC View { get; private set; }

        public View BaseRootView
        { 
            get
            { 
                if (View != null)
                {
                    return View.View;
                }

                return null;
            }
        }

        protected ViewControllerMC ModelController
        {
            get
            {
                return GetModelController();
            }

            private set
            {
                SetModelController(value);
            }
        }

        // устанавливаем подконтрольную вьюху и потом ищем в ней нужные контролы
        public void SetControlledView(View view, ViewMC model)
        {
            View = new ViewVC(Activity, model, view);
        }

        public virtual void BindWithModel(ViewControllerMC model)
        {
            if (IsAliveModel)
            {
                UnbindFromModelController();
            }

            ModelController = model;
            ModelUUID = ModelController.UUID;

            FindViews();
            InitByModel();
            ApplyDefaultInterfaceSettings();
            LocalizeUserInterface();
            SubscribeToViewEvent();
            SubscribeToModelEvent();
        }
            
        public virtual void AddViewToParent(View parentView)
        {
            if (View == null || View.View == null)
            {
                return;
            }

            var currentParent = (ViewGroup)View.View.Parent;

            if (currentParent != null)
            {
                if (!ReferenceEquals(currentParent, parentView))
                {
                    currentParent.RemoveView(View.View);
                }
                else
                {
                    return;
                }
            }

            if (parentView is ViewGroup)
            {
                (parentView as ViewGroup).AddView(View.View, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent));
            }
        }

        public void HideSoftInput(IBinder binder)
        {
            if (!IsDestroyed)
            {
                var inputManager = (InputMethodManager)Activity.GetSystemService(Activity.InputMethodService);
                inputManager.HideSoftInputFromWindow(binder, 0);
            }
        }

        public void ShowSoftInput(View view, ShowFlags flags)
        {
            var inputManager = (InputMethodManager)Activity.GetSystemService(Activity.InputMethodService);
            inputManager.ShowSoftInput(view, flags);
        }

        public virtual void RemoveFromParent()
        {
            // здесь удаляется представление из родителя, андроид это позволяет.
            // а удаление из словаря родителя CompilanceControllers.Remove(controller.ModelController), если необходимо,
            // происходит по дополнительному запросу события от чилда AskRemoveControllerFromParent() в перекрытом этом методе
            // а также в перекрытом методе анимируем чилд относительно роидителя как хотим.
            // Можно не удалять а скрывать за экарн.

            // бывает ситуация когда вьюха уже уничтожена,
            // и после этого по событию окончания анимации срабатывает удаление вьюхи с экрана
            if (View == null || View.View == null)
            {
                return;
            }

            View.RemoveFromParentView();
        }

        protected virtual View LoadBaseView(ViewControllerMC model)
        {
            View view = null;
            var layoutId = ResourceHelper.GetIdByName(model.ViewControllerId);

            if (layoutId > 0)
            {
                view = activity.LayoutInflater.Inflate(layoutId, null);
            }

            return view;
        }

        protected virtual void InitByModel()
        {
            InitNavigationItem();
            PresentationStyle = WindowPresentationStyle.Center;
        }

        protected virtual void FindViews()
        {
        }

        protected virtual void SubscribeToModelEvent()
        {
            ModelController.WillDestroy += HandleWillDestroy;
            ModelController.InsetViewChanged += HandleInsetViewChanged;
            ModelController.FromParentViewControllerRemoved += HandleFromParentViewControllerRemoved;
            ModelController.ViewDestroyed += HandleModelControllerViewDestroyed;
            ModelController.Dismissed += HandleViewControllerDismissed;
            //// ModelController.IsEditingChanged += HandleModelControllerIsEditingChanged;

            ModelController.ViewOverlaid += HandleViewOverlaid;
            ModelController.ViewControllerPresented += HandleViewControllerPresented;
            ModelController.PopoverControllerChaned += HandlePopoverControllerChaned;
            //// ModelController.CultureChanged += HandleCultureChanged;
            ModelController.InSightChanged += HandleModelControllerInSightChanged;
        }
            
        protected virtual void UnsubscribeFromModelEvent()
        {
            ModelController.WillDestroy -= HandleWillDestroy;
            ModelController.InsetViewChanged -= HandleInsetViewChanged;
            ModelController.FromParentViewControllerRemoved -= HandleFromParentViewControllerRemoved;

            ModelController.ViewDestroyed -= HandleModelControllerViewDestroyed;
            ModelController.Dismissed -= HandleViewControllerDismissed;

            ModelController.ViewOverlaid -= HandleViewOverlaid;
            ModelController.ViewControllerPresented -= HandleViewControllerPresented;
            ModelController.PopoverControllerChaned -= HandlePopoverControllerChaned;
            ModelController.InSightChanged -= HandleModelControllerInSightChanged;
        }
            
        protected virtual void SubscribeToViewEvent()
        {
            View.View.ViewAttachedToWindow += HandleViewAttachedToWindow;
            View.View.ViewDetachedFromWindow += HandleViewDetachedFromWindow;
        }

        protected void SafetyInvokeOnMainThread(Action action)
        {
            if (IsDestroyed)
            {
                return;
            }

            if ((Activity != null) && (action != null))
            {
                Activity.RunOnUiThread(action);
            }
        }

        private void PresentViewController(ViewControllerMC model)
        {
            ViewControllerFactory.Instance.RunDialogContainerByModelSafety(App.Instance.ActivityInstance, model);
        }
    }
}

Actually the whole application is built to work with these view controllers. I will say right away that this is quite convenient in Android. We can put each control or group into a separate ViewControllerVC and the logic of working with its views and do what we want with it - present it in a separate window, open a detailed window, and go back. Those. the whole work is very similar to working with UIViewControllerVC in iOs, completely abstracting from Activity and Fragment, using them only as navigation Pages - containers. Now we decided to try to move or rather transfer all this to MVVMcross. it seems like it's a proven architecture, debugged and so on.
What are the analogues of ViewControllerVC in MVVMcross in Abndroid? Maybe somehow you can inherit ViewControllerVC from IMvxAndroidView and create your own presenter for it.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
V
VitCher, 2016-05-12
@VitCher

In general, the ViewControllerMc model in our architecture, as I understand it, is an analogue of MvxViewModel,
the controller is for iOs MvxViewController and for android MvxActivity.
But MvxActivty is suitable for us only for the root of the general page, but all the children included inside - how they can be released so that they also work with MvxViewModel models. As I already said, is it possible to add your own type, inherit ViewControllerVC from IMvxAndroidView and create your own presenter for it.
I will give another example to understand our architecture. For example, there is a home screen and in it we add a title. In code it looks like this:

public class HomeScreenVC : ViewControllerVC
    {
        public HomeScreenVC(Activity activity, ViewControllerMC model) : base(activity, model)
        {
        }

        public HomeScreenTopBarVC TopBar { get; set; }

        protected override void InitByModel()
        {
            base.InitByModel();

            TopBar = (HomeScreenTopBarVC)ViewControllerFactory.Instance.GetOrCreateViewControllerByModel(Activity, ModelController.TopBar, BaseRootView.FindViewById<LinearLayout>(Resource.Id.HomeTopBarLayout));
        }

...

}

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question