Подтвердить что ты не робот

Невозможно выполнить это действие внутри onLoadFinished

Я создаю новое приложение для Android с одним действием и многими фрагментами.

Сначала я запускаю "OverviewFragment" (моя панель инструментов), которая должна показывать некоторые основные сведения. My OverviewFragment загружает информацию с помощью "AccountProvider" (ContentProvider). Если база данных пуста, обзорFragment должен быть заменен моим WelcomeFragment...

Вот какой код:

public class OverviewFragment extends BaseFragment
    implements LoaderManager.LoaderCallbacks {

private static final int LOADER_ACCOUNTS = 10;
private static final int LOADER_TRANSACTIONS = 20;

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    getLoaderManager().initLoader(LOADER_ACCOUNTS, null, this);
    //getLoaderManager().initLoader(LOADER_TRANSACTIONS, null, this);
}

@Override
public Loader onCreateLoader(int id, Bundle args) {
    Uri uri = null;
    String[] projection = null;
    String selection = null;
    String[] selectionArgs = null;
    String sortOrder = null;

    switch (id) {
        case LOADER_ACCOUNTS:
            uri = WalletProvider.CONTENT_URI;
            projection = new String[]{DBHelper.ACC_ID, DBHelper.ACC_TITLE};
            sortOrder = DBHelper.ACC_TITLE;
            break;
    }
    CursorLoader cl =  new CursorLoader(getActivity(),
            uri, projection, selection, selectionArgs, sortOrder);
    return cl;
}

@Override
public void onLoadFinished(Loader loader, Object data) {
    switch (loader.getId()) {
        case LOADER_ACCOUNTS:
            bindAccounts((Cursor) data);
            break;
    }

}

@Override
public void onLoaderReset(Loader loader) {

}

private void bindAccounts(Cursor cursor) {
    boolean showCreateWallet = true;


    if (cursor != null && cursor.moveToFirst()) {
        showCreateWallet = false;
    }

    if (showCreateWallet) {
        listener.changeFragment(new WalletCreateFragment());
    }
}

и здесь моя основная деятельность

    @Override
public void changeFragment(Fragment fragmentToLoad) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.beginTransaction()
            .replace(R.id.container, fragmentToLoad)
            .commit();
}

Теперь... если я запускаю свое приложение с пустой базой данных, я получаю ошибку, которую вы видите в заголовке.

Я знаю, что я не должен изменять фрагмент в функции onLoadFinished.... но где я могу это сделать?: P

Извините за мой английский =)

4b9b3361

Ответ 1

Это ошибка андроида. Чтобы решить эту проблему, используйте обработчик для замены/добавления/вызова фрагмента внутри onLoadFinished следующим образом:

final int WHAT = 1;
Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {                    
        if(msg.what == WHAT) changeFragment(fragmentToLoad);            
    }
};
handler.sendEmptyMessage(WHAT);

Ответ 2

Это не ошибка, но это не простая проблема. onLoadFinished может запускаться в любое время, в том числе после сохранения состояния активности. После этого изменение фрагмента автоматически не будет сохранено каркасом. Вы должны явно разрешить это с помощью commitAllowingStateLoss

В документации onLoadFinished:

Вызывается, когда ранее созданный загрузчик завершил загрузку. Обратите внимание, что обычно приложению не разрешается совершать транзакции фрагментов во время этого вызова, так как это может произойти после сохранения состояния активности. См. FragmentManager.openTransaction() для дальнейшего обсуждения этого вопроса.

Цитата Dianne Hackborn из поток, упомянутый в комментарии к другому ответу:

Просто поймите, что если состояние фрагмента активности уже сохранено, вашему коду нужно будет его правильно обновить, если он будет перезапущен позже из состояния.

[...]

Это плохой пользовательский интерфейс, чтобы отобразить диалог (или сделать любой другой значительный сдвиг в пользовательском интерфейсе) в результате загрузки. Вот что вы делаете: выставляя какую-то операцию для запуска в фоновом режиме в течение определенного времени, которое после завершения может бросить что-то перед пользователем, вытаскивая их из того, что они делают.

Мое предложение состоит в том, чтобы показать пользователю любую информацию о результате загрузчика в строке так же, как вы могли бы отображать данные из него.

Если вы (и ваши пользователи) в порядке с пользовательским интерфейсом, вы можете использовать commitAllowingStateLoss для обновления фрагмента.

Ответ 3

В обоих ответах отсутствует хорошее решение. Это правда, что неплохо было что-то сделать на onLoadfinished, поскольку действие или фрагмент могли быть уничтожены.

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

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

В вашем примере вы расширяете Callbacks непосредственно с помощью Fragment, чтобы вы могли создать метод на своем фрагменте, который позволяет безопасно выполнять обновление пользовательского интерфейса следующим образом:

private void update(Runnable runnable) {
    View rootView = getView();
    if (isAdded() && rootView != null) {
        rootView.post(runnable);
    }
}

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

update(new Runnable() {
    @Override
    public void run() {
        bindAccounts((Cursor) data);
    }
})

Тем не менее, это не очень разумная идея использовать ваш фрагмент в качестве обратных вызовов, так как он может пропустить ваш фрагмент в LoaderManager, который живет в Activity.

Еще один безопасный способ обращения с ними - с широковещательными приемниками (убедитесь, что они незарегистрированы).

Однако самым безопасным вариантом будет использование недавно анонсированных компонентов архитектуры для Android, когда они будут готовы к производству:

https://developer.android.com/topic/libraries/architecture/index.html