Как стать автором
Обновить

Ожидание пользовательской реакции на Dialog в Android

Время на прочтение3 мин
Количество просмотров5.3K
Прежде чем что-то изобретать, я, конечно, погуглил (автор этого поста уже после выложил своё решение).
Дело в том, что в UI потоке нельзя ждать, а Activity предоставляет событийную модель работы с диалогами: по закрытию диалога вызывается общий обработчик с идентификатором диалога. Такой подход мне показался не очень удобным и я решил сделать все по-своему.
Раз нельзя заставлять ждать UI поток — создадим другой поток и заставим ждать его, а раз так, то мне понадобится механизм синхронизации потоков. Для этой цели я написал элементарный мьютекс. Метод lock() не возвращает управление до тех пор пока не будет вызван unlock().
public class Mutex {
    public synchronized void lock() throws InterruptedException {
        this.wait();
    }
    public synchronized void unlock() {
        this.notify();
    }
}

Далее я создал класс для работы с диалогом и включил в него мьютекс. Класс инициализирует диалог и хранит реакцию пользователя после завершения работы с диалогом. Я опустил реализиацию некоторых методов класса, чтобы сфокусироваться только на важной части реализации.
Всем кнопкам назначены обработчики(1), которые сохраняют возвращаемое значение в переменной, а после этого закрывают диалог. Обработчик закрытия диалога(2) освобождает мьютекс. Интерфейсная функция(3) ожидает освобождения мьютекса.
public class SyncDialog {
    private Dialog mDialog;
    private Mutex mMutex;
    private int mResult;
    private Button mYesButton;
    private Button mNoButton;
    private Button mCancelButton;
    
    public SyncDialog(Context context) {
        mMutex = new Mutex();
        mDialog = new Dialog(context);
        mDialog.setContentView(R.layout.dialog);
        findViews();
        
        mYesButton.setOnClickListener(new OnClickListener() {    // (1)
            public void onClick(View v) {
                mResult = YES_RESULT;
                mDialog.dismiss();
            }
        });
        
        mNoButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                mResult = NO_RESULT;
                mDialog.dismiss();
            }
        });
        
        mCancelButton.setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                mResult = CANCEL_RESULT;
                mDialog.cancel();
            }
        });
        
        mDialog.setOnDismissListener(new OnDismissListener() {    // (2)
            public void onDismiss(DialogInterface dialog) {
                mMutex.unlock();    
            }
        });
    }
    
    public void waitForResponse() throws InterruptedException {    // (3)
        mMutex.lock();
    }
}

Ну и, собственно, то, чего я изначально хотел: метод, который ждет реакции пользователя(2). Он должен работать в отдельном потоке.
Сначала мы создаем SyncDialog в UI потоке(1), затем, в фоновом потоке, отправляем на исполнение в UI поток инициализацию и запуск диалога(3), ждем(4) и обрабатываем результат(5).
public class MyActivity extends Activity {
    private SyncDialog syncDialog;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        syncDialog = new SyncDialog(this);                // (1)
    }
    
    public void processDialogs(String... questions) {    // (2)
        
        for (final String question : questions) {
            runOnUiThread(new Runnable() {                // (3)        
                public void run() {
                    syncDialog.setText(question);
                    syncDialog.show();                        
                }
            });
            
            int response;
            try {
                syncDialog.waitForResponse();            // (4)
                response = syncDialog.getResult();
            } catch (InterruptedException e) {
                response = SyncDialog.CANCEL_RESULT;
            }
            
            if (response == SyncDialog.YES_RESULT) {    // (5)
                Log.i(question, "Yes");
            }else if (response == SyncDialog.NO_RESULT) {
                Log.i(question, "No");
            }else
                break;
        }
    }
}

Запустить processDialogs() можно при помощи AsyncTask
public class DialogsTask extends AsyncTask<String, Void, Void> {
    @Override
    protected Void doInBackground(String... params) {
        processDialogs(params);
        return null;
    }
}

Для последовательного отображения диалоговых окон используя событийный подход, мне в голову пришла только идея создать список из структур, хранящих данные для отображения диалога, и, по закрытию диалога, инициализировать новый диалог следующей в списке структурой. Этот метод мне показался совсем неудобным. Надеюсь, статья окажется полезной тем кто искал решение подобной проблемы.

Спасибо за внимание.

Полный исходный текст примера можно посмотреть тут.
Теги:
Хабы:
Всего голосов 23: ↑18 и ↓5+13
Комментарии13

Публикации

Истории

Работа

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань