Этот пост в блоге посвящен следующим вопросам:
1. Почему иногда требуется блокировка интерфейса и ожидание ввода пользователя?
2. Как создать модал оповещения, который ожидает ввода пользователя на Android с помощью MonoDroid (Xamarin.Android)
проблема
У меня есть бизнес-логика (контроллеры), которые предполагают, что показ окна подтверждения (alertDialog) является блокирующим вызовом пользовательского интерфейса. Это не нужно менять, пока мне нужно выполнить требование и показать экраны с полем подтверждения.
Детали проблемы
Я видел все аргументы ( здесь и здесь ) и во многих других местах о том, как плохо блокировать пользовательский интерфейс, чтобы ждать обратной связи, и я не могу согласиться больше.
Тем не менее, быть хорошим или плохим — это одно, а наличие требования для конкретной функции (блокировка диалогового окна предупреждения пользовательского интерфейса) — это другое.
Я разработал приложение для Android с использованием MonoDroid (Xamarin.Android), и оно использует шаблон проектирования MVC. Все экраны спроектированы как фиктивные фрагменты (классы), которые связаны с контроллерами.
Контроллеры являются основной частью бизнеса для этого клиента. Эти контроллеры были разработаны для Windows, но у них есть только Business Logic, для меня требовалось создать аналогичное приложение для Android (аналогичное Windows), которое будет использовать MVC и использовать эти контроллеры без изменений (или минимально возможного) ,
Это понятно, учитывая тот факт, что эти контроллеры (включая алгоритмы, библиотеки и т. Д.) Были протестированы, использованы и доверены.
Я разработал приложение достаточно хорошо, без необходимости менять какой-либо контроллер, и все работало хорошо, пока у меня не было подтверждения.
Стиль Windows для MessageBox или Confirm Box является синхронным, что означает, что он блокирует пользовательский интерфейс и ждет, пока пользователь нажмет Да / Нет. Это было предположение, которое проникло в эти контроллеры, и мне нужно найти способ выполнить это.
Даже когда я портировал приложение на iOS, у меня не было никаких проблем с этим, поскольку iOS использует механизм блокировки пользовательского интерфейса для отображения диалоговых окон Alert / Confirm (Modal).
Тем не менее, Android использует Asynch подход, чтобы справиться со всем, что связано с пользовательским интерфейсом. Это хорошая новость с точки зрения пользовательского опыта, но для меня это ужасная новость, что я пообещал сделать это приложение и максимально приблизить его к Windows и iOS «без смены контроллеров».
Я начал искать варианты, нашел вопросы и ответы, упомянутые выше, на SO и других сайтах, и все они говорят об одном и том же, если вам нужно передать обратный вызов в alertDialog. Это не вариант для меня, потому что контроллеры предполагают, что у нас есть блокирующий вызов пользовательского интерфейса, когда нам требуется подтверждение от пользователя.
Чтобы продемонстрировать это, давайте посмотрим на обработчик событий Logout Button.
// logout call on Windows/iOS public void Logout() { var args = new ConfirmEventArgs("Are you sure you want to logout?", "Logout?"); OnConfirmUser(args); if (!args.Cancel) { DataManager.IsUserLoggingOff = true; OnApplyWorkflow(new StimulusEventArgs(DataObject as IWorkflowDomain, Constants.Stimuli.Logout)); } }
Я нашел (http://stackoverflow.com/questions/2028697/dialogs-alertdialogs-how-to-block-execution- while-dialog-is-up-net-style#10358260) этот ответ на SO, который некоторые люди говорят он работает на Android с использованием Java, но на Xamarin.Android это не сработало. Причина заключалась в том, что этот подход предполагал, что мы отправляем сообщения на более низком уровне между потоками, а затем выкидываем исключение из одного и затем перехватываем это исключение в другом потоке.
Это не сработало на Xamarin.Android, и когда его попросили о помощи у команды Xamarin, все, что они сделали, это показали мне пример кода, где вам нужно предоставить обратный вызов для MessageBox, как если бы я его раньше не видел.
Решение
После долгих раздумий я пришел к своему подходу, который в основном создает поток для подтверждения и ПОДОЖДИТЕ для ввода пользователя.
Причина, по которой нам нужен новый поток, состоит в том, чтобы избежать блокировки основного потока пользовательского интерфейса, пока мы ожидаем ввода пользователя.
Конечный результат был примерно таким:
// In the BaseController, I added this method, which checks for platform and creats a new thread on Android only // (since the controllers code is shared cross-platforms) protected void RunConfirmAction(Action runnableAction) { if (runnableAction != null) { if (Core.Platform.IsAndroid) { var confirmThread = new Thread(() => runnableAction()); confirmThread.Start(); } else { runnableAction(); } } } // The call to the logout method has now changed like this: RunConfirmAction(Logout); // the implemtation of the MessageBox waiting is like this: public DialogResult MessageBoxShow(string message, string caption, MessageBoxButtons buttons, MessageBoxIcon icon, MessageBoxDefaultButton defaultButton) { if (_CurrentContext != null && _CurrentContext.Screen != null && MainForm.MainActivity != null) { Action<bool> callback = OnConfirmCallBack; _IsCurrentlyInConfirmProcess = true; Action messageBoxDelegate = () => MessageBox.Show(((Activity)MainForm.MainActivity), callback, message, caption, buttons); RunOnMainUiThread(messageBoxDelegate); while (_IsCurrentlyInConfirmProcess) { Thread.Sleep(1000); } } else { LogHandler.LogError("Trying to display a Message box with no activity in the CurrentContext. Message was: " + message); } return _ConfirmBoxResult ? DialogResult.OK : DialogResult.No; } private void OnConfirmCallBack(bool confirmResult) { _ConfirmBoxResult = confirmResult; _IsCurrentlyInConfirmProcess = false; } private bool _ConfirmBoxResult = false; private bool _IsCurrentlyInConfirmProcess = false;
Код для класса MessageBox можно найти здесь, я создал это давно, и он прекрасно работает для проектов с зелеными полями, где вы можете отправлять обратные вызовы в поле подтверждения.
И все, после всего этого, я получил что-то, удовлетворяющее требованиям, и мой клиент был очень рад, что им не нужно менять свои контроллеры (что означает меньше ошибок, меньше тестирования, более быструю доставку).
Таким образом, я надеюсь, что у меня есть хорошие основания для того, почему иногда необходимо иметь блокирующий пользовательский интерфейс alertDialog на Android и как это можно сделать.
Если у вас есть какие-либо вопросы, или вы думаете, что это можно сделать лучше, лучше или проще, я хотел бы услышать от вас.