小部件更新颤动时整页重新加载

问题描述 投票:0回答:1

我的 flutter 应用程序遇到问题 - 我有一个带有小部件 ListView 的屏幕,每个小部件都有一个“喜欢”按钮,当按下“喜欢”按钮时,应用程序会读取 Riverpod 提供程序并更新应用程序状态。问题是,当此更新发生时,整个屏幕都会重新加载,而不仅仅是发生更改的小部件。我在另一个屏幕上使用相同的按钮小部件,但没有遇到此问题。非常感谢任何帮助。

这是屏幕代码:

class FeedScreen extends ConsumerWidget {
  const FeedScreen({super.key, required this.currentUserId});
  final UserID currentUserId;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(),
      body: ResponsiveCenter(
              padding: const EdgeInsets.all(8.0),
              child: Column(
                  mainAxisAlignment: MainAxisAlignment.start,
                  mainAxisSize: MainAxisSize.max,
                  children: [
                    Expanded(child: FeedList(currentUserUid: currentUserId,)),
                  ]))
    );
  }
}
class FeedList extends ConsumerWidget {
  const FeedList({super.key, required this.currentUserUid});
  final String currentUserUid;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    debugPrint("FEED LIST");
    final feedListVal = ref.watch(fetchFeedProvider);
    // ScrollController controller = ScrollController();

    return AsyncValueWidget(
      value: feedListVal,
      data: (feedListVal) {
        return feedListVal.isEmpty
          ? const Center(child: Text("Feed is empty"))
          : ListView.builder(
              itemCount: feedListVal.length,
              itemBuilder: (BuildContext context, int index) {
                      return FeedItem(timelineItem: feedListVal[index], userId: currentUserUid);
              });
      }
    );
  }
}
class FeedItem extends StatelessWidget {
  const FeedItem({
    super.key,
    required this.timelineItem,
    required this.userId
  });

  final TimelineItem timelineItem;
  final String userId;
  
  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: () {
        context.goNamed(
          AppRoute.eventDetails.name,
          pathParameters: {
            'eventId': timelineItem.eventId
          }
        );
      },
      child: Center(
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Card(child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              children: [
                Row(
                  children: [
                    CircleAvatar(backgroundImage: NetworkImage(timelineItem.authorPfpUrl),),
                    Padding(
                      padding: const EdgeInsets.all(4.0),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            timelineItem.event != null
                              ? '${timelineItem.authorName} added content to ${timelineItem.event!.name}'
                              : timelineItem.authorName,
                            textAlign: TextAlign.start,),
                          Text(Jiffy.parseFromDateTime(timelineItem.createdAt).fromNow(), style: const TextStyle(color: Colors.grey),)
                        ],
                      ),
                    ),
                  ],
                ),
                const Divider(),
                Text(timelineItem.text),
                if (timelineItem.imageUrls.isNotEmpty)
                  // TODO: make this show more than one image or expand to view all images
                  Image.network(timelineItem.imageUrls[0]),
                const Divider(),
                Row(
                  mainAxisSize: MainAxisSize.max,
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text("${timelineItem.likes.length} Likes", textAlign: TextAlign.left,),
                    Text("${timelineItem.comments.length} Comments", textAlign: TextAlign.right,)
                  ],
                ),
                const Divider(),
                Row(
                  mainAxisSize: MainAxisSize.max,
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    LikeTimelineItemButtonWidget(
                      eventId: timelineItem.eventId,
                      timelineItemId: timelineItem.id,
                      currentUserUid: userId,
                      likedByIds: timelineItem.likes
                    ),
                    ElevatedButton.icon(
                      onPressed: () {
                        // _showFullModal(context, timelineItem.id);
                      }, 
                      icon: const Icon(Icons.comment), 
                      label: const Text("Comment")
                    )
                  ],
                )
              ],
            ),
          )),
        ),
      )
    );
  }
}

点赞按钮小部件代码:

class LikeTimelineItemButtonWidget extends ConsumerWidget {
  const LikeTimelineItemButtonWidget({
    // required this.uid,
    required this.eventId,
    required this.timelineItemId,
    required this.currentUserUid,
    required this.likedByIds,
    super.key,
  });

  // final UserID uid;
  final EventID eventId;
  final String timelineItemId;
  final UserID currentUserUid;
  final List<UserID> likedByIds;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    ref.listen<AsyncValue>(
      likeTimelineItemControllerProvider,
      (_, state) => state.showAlertDialogOnError(context)
    );

    final state = ref.watch(likeTimelineItemControllerProvider);
    final liked = likedByIds.contains(currentUserUid);
    
    if (liked) {
      return ElevatedButton.icon(
        style: ElevatedButton.styleFrom(
          backgroundColor: Theme.of(context).primaryColor.withOpacity(0.4),
        ),
        onPressed: () => {
          debugPrint("like button pressed"),
          ref
            .read(likeTimelineItemControllerProvider.notifier)
            .likeTimelineItem(eventId, timelineItemId, currentUserUid, likedByIds)
        },
        icon: Icon(Icons.thumb_up),
        label: Text("Like")
      );
    } else {
      return ElevatedButton.icon(
        onPressed: () => {
          debugPrint("like button pressed"),
          ref
            .read(likeTimelineItemControllerProvider.notifier)
            .likeTimelineItem(eventId, timelineItemId, currentUserUid, likedByIds)
        },
        icon: Icon(Icons.thumb_up),
        label: Text("Like")
      );
    }
  }
}

点赞按钮控制器的代码:

@riverpod
class LikeTimelineItemController extends _$LikeTimelineItemController with NotifierMounted {
  @override
  FutureOr<void> build() {
    ref.onDispose(setUnmounted);
  }

  Future<bool> likeTimelineItem(EventID eventId, String timelineItemId, UserID likingUserID, List<UserID> likedByIds) async {
    try {
      final timelineItemsRepository = ref.read(timelineItemsRepositoryProvider);
      state = const AsyncLoading();
      final value = await AsyncValue.guard(
        () => timelineItemsRepository.toggleLikeTimelineItem(timelineItemId, likingUserID, eventId, likedByIds));
      final success = value.hasError == false;
      if (mounted) {
        state = value;
      }
      return success;
    } catch (e, st) {
      debugPrint(e.toString());
      debugPrint(st.toString());
      if (mounted) {
        state = AsyncError(e, st);
      }
      return false;
    }
  }
}

以及提取屏幕内容的提供者的代码:

@riverpod
Future<List<TimelineItem>> fetchFeed(FetchFeedRef ref) async {
  final user = await ref.watch(watchUserProvider.future);
  debugPrint("[FEED SERVICE - fetchFeed] - user: $user");
  final friends = await ref.watch(friendsListStreamProvider(user).future);
  debugPrint("[FEED SERVICE - fetchFeed] - friends: $friends");
  return friends.isEmpty
      ? []
      : await ref.watch(
          watchFeedProvider(friends.map((user) => user.id).toList()).future);
}

控制器的代码:

Future<bool> likeTimelineItem(EventID eventId, String timelineItemId, UserID likingUserID, List<UserID> likedByIds) async {
    try {
      final timelineItemsRepository = ref.read(timelineItemsRepositoryProvider);
      state = const AsyncLoading();
      final value = await AsyncValue.guard(
        () => timelineItemsRepository.toggleLikeTimelineItem(timelineItemId, likingUserID, eventId, likedByIds));
      final success = value.hasError == false;
      if (mounted) {
        state = value;
      }
      return success;
    } catch (e, st) {
      debugPrint(e.toString());
      debugPrint(st.toString());
      if (mounted) {
        state = AsyncError(e, st);
      }
      return false;
    }
  }
flutter riverpod
1个回答
0
投票

这已经是很多代码了,但我认为还需要更多代码才能明确地告诉问题出在哪里。但是,您提供的代码中有一个可以开始的地方。

在您的 FeedList 小部件中,您有以下行:

final feedListVal = ref.watch(fetchFeedProvider);

这意味着 FeedList 小部件将在每次 fetchFeedProvider 触发时重建。这是需要更多信息来确定的地方,但一个有效的假设是,只要它提供的数据发生任何变化(其中包括

friendsListStreamProvider(user)
状态),就会重新触发
like

如果是这种情况,那么你必须弄清楚你要做什么。这将取决于您的数据库以及您如何实施您的提供商。

实际上,只需在列表项上添加

ValueKey
以及 ID 和任何可能更改的属性就足够了,因为这将阻止列表项在不需要时重新呈现。

但是,如果您根本不想调用该构建函数,解决方案将如下所示:

  1. 将数据库与提供者的获取分离,并确保调用缓存在内存中(可能已经由您的数据库解决方案完成)
  2. 使 fetchFeedProvider 返回 ID 列表而不是所有数据,并确保提供程序在内部数据更改时不会重新触发
  3. ValueKey
    添加到以项目 ID 为键的 FeedItem 对象中,并且仅传入 ID(这应该确保即使再次调用构建函数,也不会触发重新布局或重新布局)绘制整个列表
  4. FeedItem
    对象中使用提供程序来获取每个项目的数据。请注意,如果它为列表中的每个项目执行单独的数据库调用,则效率可能会很低,但如果您已使用缓存将数据库调用和提供程序解耦,则可以使用它,以便 fetchFeedProvider 仍然执行数据库调用,并且缓存保存随后将被请求的所有项目。

如果您执行了所有这些操作,那么不仅在项目更改时整个列表不会重建,而且如果确实如此,列表也不应该重新渲染。需要注意的一件事是,您可能无法仅在提供程序中使用列表 - 默认情况下,它们不会实现任何类型的相等,因此两个不同的列表即使具有相同的元素也永远不会相等。您可能需要将其包装在提供适当相等性的内容中,或者使用

built_collection
包中的内置列表之类的内容。

© www.soinside.com 2019 - 2024. All rights reserved.