Loader → Flow + ViewModel

7 阅读1分钟

Loader → Flow + ViewModel

老写法(Java)

public class MyActivity extends AppCompatActivity
        implements LoaderManager.LoaderCallbacks<Cursor> {

    private SimpleCursorAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        adapter = new SimpleCursorAdapter(this, R.layout.item_row, null,
                new String[]{"name"}, new int[]{R.id.tv_name}, 0);
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        return new CursorLoader(this, MyContentProvider.CONTENT_URI,
                null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        adapter.swapCursor(cursor);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        adapter.swapCursor(null);
    }
}

问题在哪里

Loader API 在 API 28 已废弃。需要 Activity 实现 LoaderCallbacks 接口,代码分散在三个回调和初始化之间。基于 Cursor,如果不用 ContentProvider 就用不了,使用场景极窄。

新写法(Room + Flow + ViewModel)

DAO:

@Dao
interface ItemDao {
    @Query("SELECT * FROM items ORDER BY name ASC")
    fun getAllItems(): Flow<List<Item>>
}

ViewModel:

class ItemViewModel(private val dao: ItemDao) : ViewModel() {
    val items: LiveData<List<Item>> = dao.getAllItems().asLiveData()
}

Activity / Fragment:

viewModel.items.observe(viewLifecycleOwner) { list ->
    adapter.submitList(list)
}

一句话注意

Flow.asLiveData() 默认在 Dispatchers.Main 上收集,不需要手动切线程。但 Room 的 Flow 查询需要在数据库线程池上执行,Room 内部已经帮你处理了线程切换,不需要再用 flowOn

如果不需要 LiveData(比如配合 Compose),直接收集 Flow 也行:

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        dao.getAllItems().collect { list ->
            adapter.submitList(list)
        }
    }
}

repeatOnLifecycle 确保只在 STARTED 以上状态收集,切换到后台自动取消,避免资源浪费。用 lifecycleScope.launch 直接 collect 的话要特别注意这一点,否则可能泄漏。


Java Android 老项目迁移系列,持续更新中。