You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Flutter中结合Riverpod使用日期范围选择器

解决Flutter中Riverpod结合日期范围选择器的问题

核心修改要点

  • 将Trip类改为支持复制更新的不可变类(添加copyWith方法)
  • 使用StateNotifier和StateNotifierProvider管理Trip列表的状态,实现状态更新与UI同步
  • 在Trip页面中通过provider更新Trip的日期范围,确保修改实时反映到界面

修改后的完整代码

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Riverpod DateRange Picker',
      home: HomeScreen(),
    );
  }
}

// 修改Trip类,添加copyWith方法用于更新日期范围
class Trip {
  final String name;
  final DateTimeRange dateTimeRange;
  
  Trip(this.name, this.dateTimeRange);

  // 生成新的Trip对象,仅更新指定字段
  Trip copyWith({DateTimeRange? dateTimeRange}) {
    return Trip(
      name,
      dateTimeRange ?? this.dateTimeRange,
    );
  }
}

// 创建StateNotifier管理Trip列表的状态
class TripListNotifier extends StateNotifier<List<Trip>> {
  TripListNotifier() : super([
    Trip('Test 1',
        DateTimeRange(start: DateTime(2023, 1, 3), end: DateTime(2023, 1, 6)))
  ]);

  // 更新指定Trip的日期范围
  void updateTripDate(Trip targetTrip, DateTimeRange newRange) {
    state = state.map((trip) {
      if (trip.name == targetTrip.name) { // 实际项目建议用唯一ID标识Trip
        return trip.copyWith(dateTimeRange: newRange);
      }
      return trip;
    }).toList();
  }
}

// 定义对应的provider
final tripListProvider = StateNotifierProvider<TripListNotifier, List<Trip>>((ref) {
  return TripListNotifier();
});

class HomeScreen extends ConsumerWidget {
  const HomeScreen({super.key});
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 从provider获取Trip列表
    final trips = ref.watch(tripListProvider);
    
    return Scaffold(
      appBar: AppBar(title: const Text('Riverpod DateRange Picker')),
      body: ListView(
        children: [
          for (final trip in trips)
            ListTile(
              title: Text(trip.name),
              onTap: () {
                Navigator.push(
                    context,
                    MaterialPageRoute<void>(
                        builder: (BuildContext context) =>
                            TripScreen(trip: trip)));
              },
            ),
        ],
      ),
    );
  }
}

class TripScreen extends ConsumerWidget {
  const TripScreen({Key? key, required this.trip}) : super(key: key);

  final Trip trip;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // 监听当前Trip的最新状态
    final currentTrip = ref.watch(tripListProvider).firstWhere((t) => t.name == trip.name);

    void _showDateRangePicker() async {
      final DateTimeRange? result = await showDateRangePicker(
        context: context,
        locale: const Locale('en', 'GB'),
        initialDateRange: currentTrip.dateTimeRange,
        saveText: 'Done',
        firstDate: DateTime(2022, 1, 1),
        lastDate: DateTime(2030, 12, 31),
      );

      if (result != null) {
        // 通过provider更新Trip的日期范围
        ref.read(tripListProvider.notifier).updateTripDate(currentTrip, result);
      }
    }

    return WillPopScope(
        onWillPop: () {
          Navigator.pop(context);
          return Future.value(false);
        },
        child: Scaffold(
            backgroundColor: Colors.white,
            appBar: AppBar(
              title: Text(currentTrip.name),
            ),
            body: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    IconButton(
                      icon: const Icon(Icons.calendar_month),
                      onPressed: _showDateRangePicker,
                    ),
                    Text(
                        'Start: ${DateFormat('dd/MM/yyyy').format(currentTrip.dateTimeRange.start)}'),
                    Text(
                        'End: ${DateFormat('dd/MM/yyyy').format(currentTrip.dateTimeRange.end)}'),
                  ],
                ))));
  }
}

关键说明

  1. Trip类的copyWith方法:由于Trip是不可变类,每次修改都需要生成新对象,避免直接修改原对象导致状态无法被Riverpod追踪。
  2. StateNotifier管理状态:TripListNotifier负责维护Trip列表,提供updateTripDate方法更新指定Trip的日期范围,状态修改后会自动通知UI刷新。
  3. UI状态同步:在TripScreen中通过ref.watch监听Trip列表变化,确保显示最新数据;选择日期后通过ref.read调用notifier的更新方法完成状态修改。

内容的提问来源于stack exchange,提问作者jcwhall

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 07:15:33