【问题标题】:Flutter load and update screen depending on a API request with Riverpod根据 Riverpod 的 API 请求,Flutter 加载和更新屏幕
【发布时间】:2022-10-25 19:39:50
【问题描述】:

我创建了一个屏幕,其中显示来自远程 API 的对象列表,并根据用户寻求的名称进行更新。由于我是 Flutter 和 Riverpod 的新手,我设法从用户​​搜索更新我的页面,但我有两个问题。

问题 1:我想在加载时实现一个 CircularProgressIndicator,因为目前没有任何东西向用户表明数据正在加载,但我不知道该怎么做。

问题 2:我想要一个带有无参数 API 请求的初始状态,但我也不知道该怎么做。

我的screen.dart

class SearchGameScreen extends HookConsumerWidget {
  const SearchGameScreen({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context, WidgetRef ref) {
/*    ref.listen<AsyncValue<void>>(boardGamesListControllerProvider,
        ((previous, state) => state.showSnackBarOnError(context)));*/
    final searchController = TextEditingController();
    final boardGameListAsync = ref.watch(boardGamesListControllerProvider);
    return Scaffold(
      body: Column(
        children: [
          Row(
            children: [
              Expanded(
                child: Container(
                  padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
                  margin: const EdgeInsets.only(bottom: 2),
                  child: TextFormField(
                    controller: searchController,
                    decoration: const InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Search a game',
                    ),
                  ),
                ),
              ),
              Container(
                height: 50,
                padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
                margin: const EdgeInsets.only(bottom: 2),
                child: ElevatedButton(
                  child: const Text('Search',
                      style: TextStyle(color: Colors.white)),
                  onPressed: () {
                    ref
                    .read(boardGamesListControllerProvider.notifier).search(searchController.text);
                  },
                ),
              ),
            ],
          ),
          Expanded(
            child: BoardGamesList(boardGames: boardGameListAsync)
          )
        ],
      ),
    );
  }
}

class BoardGamesList extends HookConsumerWidget {
  const BoardGamesList({Key? key, required this.boardGames}) : super(key: key);
  final List<BoardGame> boardGames;
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ListView.builder(
      itemCount: boardGames.length,
      itemBuilder: (context, index) {
        final boardGame = boardGames[index];
        return BoardGameItemWidget(boardGame: boardGame);
      },
    );
  }
}

class BoardGameItemWidget extends ConsumerWidget {
  const BoardGameItemWidget({Key? key, required this.boardGame})
      : super(key: key);
  final BoardGame boardGame;
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return GestureDetector(
      onTap: () {
        context.go('/game/details/${boardGame.idFromApi}');
      },
      child: Card(
        margin: const EdgeInsets.all(8),
        elevation: 8,
        child: Row(
          children: [
            Hero(
              tag: boardGame.title,
              child: CachedNetworkImage(
                imageUrl: boardGame.image,
                placeholder: (context, url) =>
                    const Center(child: CircularProgressIndicator()),
                errorWidget: (context, url, error) => const Icon(Icons.error),
                width: 100,
                height: 100,
                fit: BoxFit.cover,
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(8),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Container(
                      padding: const EdgeInsets.only(bottom: 8),
                      child: Text(boardGame.title,
                          style: const TextStyle(
                              fontWeight: FontWeight.bold, fontSize: 20))),
                ],
              ),
            )
          ],
        ),
      ),
    );
  }
}

我的view_model.dart

final boardGamesListControllerProvider =
StateNotifierProvider<BoardGameList,List<BoardGame>>((ref) {
  return BoardGameList([], ref);
});

class BoardGameList extends StateNotifier<List<BoardGame>> {
  BoardGameList(List<BoardGame> items, this.ref) : super(items);

  final Ref ref;

  Future<void> search(String request) async {
    state = await ref.read(remoteApiProvider).getBoardGames(request);
  }
}

【问题讨论】:

    标签: flutter dart riverpod


    【解决方案1】:

    我找到了解决方案:

    我的屏幕:

    class SearchGameScreen extends HookConsumerWidget {
      const SearchGameScreen({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context, WidgetRef ref) {
        final searchController = TextEditingController();
        AsyncValue<List<BoardGame>> search = ref.watch(boardGamesListControllerProvider);
    
        return Scaffold(
          body: Column(
            children: [
              Row(
                children: [
                  Expanded(
                    child: Container(
                      padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
                      margin: const EdgeInsets.only(bottom: 2),
                      child: TextFormField(
                        controller: searchController,
                        decoration: const InputDecoration(
                          border: OutlineInputBorder(),
                          labelText: 'Search a game',
                        ),
                      ),
                    ),
                  ),
                  Container(
                    height: 50,
                    padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
                    margin: const EdgeInsets.only(bottom: 2),
                    child: ElevatedButton(
                      child: const Text('Search',
                          style: TextStyle(color: Colors.white)),
                      onPressed: () {
                        ref.read(boardGamesListControllerProvider.notifier).search(searchController.text);
                      },
                    ),
                  ),
                ],
              ),
              Expanded(
                child: search.when(
                  data: (data) => BoardGamesList(boardGames: data),
                  loading: () => const Center(
                      child: CircularProgressIndicator(),
                  ),
                  error: (error, _) => const Center(
                    child: Text('Uh oh... Something went wrong...',
                        style: TextStyle(color: Colors.white)),
                  ),
                )
              )
            ],
          ),
        );
      }
    }
    

    我的视图模型:

    final boardGamesListControllerProvider =
    StateNotifierProvider<BoardGameList, AsyncValue<List<BoardGame>>>((ref) {
      return BoardGameList(const AsyncValue.data([]), ref);
    });
    
    class BoardGameList extends StateNotifier<AsyncValue<List<BoardGame>>> {
      BoardGameList(AsyncValue<List<BoardGame>> items, this.ref) : super(items){
        init();
      }
    
      final Ref ref;
    
      Future<void> init() async {
        state = const AsyncValue.loading();
        try {
          final search = await ref.watch(boardGamesListProvider('').future);
          state = AsyncValue.data(search);
        } catch (e) {
          state = AsyncValue.error(e);
        }
      }
    
      Future<void> search(String request) async {
        state = const AsyncValue.loading();
        try {
          final search = await ref.watch(boardGamesListProvider(request).future);
          state = AsyncValue.data(search);
        } catch (e) {
          state = AsyncValue.error(e);
        }
      }
    }
    

    【讨论】:

      【解决方案2】:

      您可以使用freezed。这个包实现了“联合类型”/“密封类”/模式匹配的能力。

      在这种情况下,您可以声明所需的多个构造函数并使用 when/map 方法读取数据。这将解决您的两个问题。

      还有一篇关于冻结的有用文章:Flutter State Management: Going from setState to Freezed & StateNotifier with Provider

      【讨论】:

      • 你好 !它与 Riverpod 兼容吗?
      • 而且我不明白为什么我需要声明多个构造函数
      • 是的,它与riverpod 兼容。从您的示例中,您如何实现 boardGameListAsync 提供程序还不是很清楚。在这种情况下,通过StateNotifierProvider 状态为freezed 很方便,例如。 boardGameState 包括各种状态:初始化、数据、加载、错误等。
      • 我很惊讶没有 Freezed 就没有更简单的解决方案
      • 更简单的解决方案是AsyncValue,但仅限于 3 个状态
      猜你喜欢
      • 2011-08-15
      • 2022-10-07
      • 1970-01-01
      • 2022-07-07
      • 2021-05-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-18
      相关资源
      最近更新 更多