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

Flutter中Provider与Consumer不匹配导致无法渲染列表及始终显示加载指示器问题求助

Hey there, let's break down and fix the three issues you're facing with your Flutter Provider setup and data rendering:

Core Problems Identified

All three issues stem from incorrect Provider usage, missing Future state handling, and a small bug in your API request URL:

  1. The Could not find the correct Provider<List> error happens because your nested FutureProvider wasn't properly exposing a List<Place> instance, and the generic type declarations were ambiguous.
  2. Data not printing/rendering is due to not handling the full lifecycle of your Future<List<Place>> (loading, success, error states).
  3. The persistent CircularProgressIndicator comes from unhandled null states and a broken API request (you had an extra space in your Google Places API URL!).

Step-by-Step Fixes

1. Remove Redundant MaterialApp Nesting

Your MapScreen was creating a new MaterialApp, but Flutter apps only need one root MaterialApp. This nesting caused context isolation, breaking Provider scope and Navigator behavior (like your back button not working correctly). Replace it with a direct child widget.

2. Simplify Provider Structure & Avoid Nested FutureProviders

Your original setup used a ProxyProvider returning a Future<List<Place>>, then wrapped it in another FutureProvider inside GetScreen—this was completely unnecessary and caused type mismatches. Instead, use FutureBuilder to directly listen to the Future's state, which is more straightforward.

3. Fix the Google Places API URL

You had an extra space at the end of your API key parameter (&key=$key ), which made your API request invalid. Removing this space ensures the request succeeds.

4. Add Full Future State Handling

We'll explicitly handle loading, success, error, and empty data states to ensure the UI updates correctly instead of sticking on the progress indicator.


Modified Working Code

Updated MapScreen & GetScreen

class MapScreen extends StatelessWidget {
  const MapScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        FutureProvider<Position?>(
          create: (context) => GeolocatorService().getLocation(),
          initialData: null,
        ),
        ProxyProvider<Position?, Future<List<Place>>>(
          // Explicitly define generic types for clarity
          update: (context, position, previousPlaces) {
            if (position == null) return Future.value([]);
            return PlacesService().getPlaces(position.latitude, position.longitude);
          },
          initialData: Future.value([]), // Prevent null errors
        )
      ],
      child: const GetScreen(), // No more nested MaterialApp
    );
  }
}

class GetScreen extends StatelessWidget {
  const GetScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final currentPosition = Provider.of<Position?>(context);
    final placesFuture = Provider.of<Future<List<Place>>>(context);

    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          onPressed: () => Navigator.pop(context),
          icon: const Icon(Icons.arrow_back_ios_new_rounded),
        ),
        title: const Text('Tìm nhà hàng'),
        elevation: 0,
        backgroundColor: Colors.purple,
        centerTitle: true,
      ),
      body: currentPosition == null
          ? const Center(child: CircularProgressIndicator())
          : FutureBuilder<List<Place>>(
              future: placesFuture,
              builder: (context, snapshot) {
                // Handle loading state
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return const Center(child: CircularProgressIndicator());
                }
                // Handle error state
                else if (snapshot.hasError) {
                  return Center(child: Text('Load failed: ${snapshot.error}'));
                }
                // Handle empty data state
                else if (!snapshot.hasData || snapshot.data!.isEmpty) {
                  return const Center(child: Text('No nearby restaurants found'));
                }
                // Handle success state - render data
                final places = snapshot.data!;
                print('Fetched ${places.length} restaurants'); // Debug print
                return Column(
                  children: <Widget>[
                    SizedBox(
                      height: MediaQuery.of(context).size.height / 3,
                      width: MediaQuery.of(context).size.width,
                      child: GoogleMap(
                        initialCameraPosition: CameraPosition(
                          target: LatLng(currentPosition.latitude, currentPosition.longitude),
                          zoom: 16.0,
                        ),
                        zoomGesturesEnabled: true,
                      ),
                    ),
                    const SizedBox(height: 10.0),
                    Expanded(
                      child: ListView.builder(
                        itemCount: places.length,
                        itemBuilder: (context, index) {
                          return Card(
                            child: ListTile(
                              title: Text(places[index].name),
                              subtitle: Text(places[index].vicinity), // Add address for better UX
                            ),
                          );
                        },
                      ),
                    ),
                  ],
                );
              },
            ),
    );
  }
}

Updated PlacesService (Fixed API URL)

class PlacesService {
  final key = 'AIzaSyDM32Kzq-Pz_MPorUAYxWgDX7qb3nt2S6M';
  Future<List<Place>> getPlaces(double lat, double lng) async {
    var response = await http.get(Uri.parse(
        // Removed the extra space after the API key
        'https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=$lat,$lng&type=restaurant&rankby=distance&key=$key'));
    var json = convert.jsonDecode(response.body);
    print('API Response: $json'); // Debug print for API data
    var jsonResults = json['results'] as List;
    return jsonResults.map((place) => Place.fromJson(place)).toList();
  }
}

Why This Works

  • The Provider error is fixed by removing the nested FutureProvider and using FutureBuilder to directly consume the Future<List<Place>> from your ProxyProvider.
  • Data now prints and renders because we're explicitly accessing the successful snapshot data and passing it to the ListView.builder.
  • The progress indicator no longer sticks around because we handle all possible states of the Future (loading, error, empty, success) and update the UI accordingly.
  • The fixed API URL ensures your request to Google Places API is valid, so you actually get data back!

内容的提问来源于stack exchange,提问作者Trung Phạm

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 21:59:08