ОЧЕНЬ МЕДЛЕННО - интерфейс обновления данных из фрагмента - PullRequest
0 голосов
/ 13 января 2020

Очевидно, есть наука о привязке данных в android, которую я явно не понимаю. Я продолжаю бороться с обновлением представлений с помощью фрагмента или модели представления, где некоторые вещи «просто работают», другие кажутся нефункциональными.

Я хочу отключить кнопку входа в систему, изменить ее текст и установить альфа после ее нажатия до Я получаю ответ API, затем меняю его обратно. Как вы можете себе представить, я не хочу, чтобы пользователь повторно отправлял запрос на аутентификацию. Предварительное связывание данных с фрагментом будет обрабатывать прослушиватель onClick (), и я либо использую butterknife для связывания элемента, либо вручную, используя findViewById (). В любом случае, когда я изменил вид, это было практически мгновенно. Теперь с привязкой данных это кажется случайным и в некоторых случаях очень медленным, в то время как другие кажутся мгновенными. Я не включаю код, который изменит его обратно, так как он не меняет вид в первую очередь.

При написании этого у меня снова и снова возникала одна и та же проблема, кнопка никогда не меняется. Наблюдатель во фрагменте вызывается (в журнале он отображается в порядке, в котором я ожидал, что он был вызван), но пользовательский интерфейс просто не обновляется. Я снова запустил его на эмуляторе, и, как ни странно, журнал наблюдателя не появляется намного позже, а затем вид обновляется, как и ожидалось, Сорта. Он не обновлялся, когда я нажимал на кнопку, но, по крайней мере, кнопка изменилась до того, как ответ API вернулся. Не то чтобы это было полезно, потому что время между нажатием кнопки и изменением пользовательского интерфейса по-прежнему можно быстро нажать. Я остановил приложение и снова запустил его, и я вернулся к исходной проблеме, он не обновлялся вообще. Между тестами не было внесено никаких изменений кода.

Я использую SingleLiveEvent без изменений из примеров архитектуры googles https://github.com/android/architecture-samples/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java

activity_main_login <- это фрагмент, а не активность, но у меня нет рефакторинг это еще. Это усечено, поэтому может не работать без контейнера макета. </p>

<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="mainViewModel"
            type="com.example.viewmodel.MainViewModel" />
    </data>
    ...
    <Button
        android:id="@+id/btnLogin"
        android:layout_width="135dp"
        android:layout_height="47dp"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="16dp"
        android:onClick="@{() -> mainViewModel.loginClicked()}"
        android:text="@string/login"
        android:textColor="#ffffff"
        android:textStyle="bold"
        android:background="#e05206"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/fingerprintSwitch"
        tools:layout_editor_absoluteX="101dp" />
    ....
</layout>

MainFragment

ActivityMainLoginBinding binding;

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    Log.d(TAG, "-> onCreateView()" );
    super.onCreateView(inflater, container, savedInstanceState);

    mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);

    getLifecycle().addObserver(mainViewModel);

    binding = DataBindingUtil.inflate(inflater, R.layout.activity_main_login, container, false);
    mView = binding.getRoot();
    binding.setMainViewModel(mainViewModel);
    binding.setLifecycleOwner(this); // Yeah this is what I forgot last time...

    return mView;
}

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    AppLog.d(TAG, "-> onViewCreated()" );
    super.onViewCreated(view, savedInstanceState);

    mainViewModel.getShowLoading().observe(getViewLifecycleOwner(), showLoading -> {
        AppLog.d(TAG, "showLoading changed");
        this.loading = true;

        binding.btnLogin.setText(R.string.loggingIn);
        binding.btnLogin.setEnabled(false);
        binding.btnLogin.setAlpha(.5f);
    });
}

MainViewModel

private SingleLiveEvent<Boolean> showLoading = new SingleLiveEvent<>();

public void loginClicked() {
    Log.d(TAG, "loginClicked()");
    showLoading.setValue(true);
    login();
}

Вот как выглядят журналы, когда он запускается и вы нажимаете кнопку входа ...

D/MainViewModel: loginClicked()
D/MainFragment: showLoading changed
D/MainViewModel: login()

1 Ответ

1 голос
/ 15 января 2020

Не то чтобы я включил код Retrofit2, который сделал / получил вызов API, но это было связано с многопоточностью. Я обернул метод, который называл ресурс синхронной модернизации, в

new Handler().post(() -> { });

Так что login () теперь выглядит примерно так

private void login() {
    new Handler().post(() -> {
        // original retrofit call
        Thread t = new Thread(() -> authResponse = restApi.doAuthSync());
        t.start();

        // Joining thread so we wait for the response
        // I believe this to be the actual culprit of the problem
        try {
            t.join();
        } catch (InterruptedException e) {
            Log.e(TAG, e.getMessage());
        }

        // handle authResponse
        ...
    });
}

Даже если сам вызов API должен выполняться сам по себе thread (и был) весь объект просто поддерживал поток пользовательского интерфейса, скорее всего, из-за thread.join (). Это заставило Databindings не обновляться. Возможно, это удалось решить с помощью Rx Java, но я этого еще не реализовал, и для простой задачи это не нужно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...