I am the new guy who learn flutter about 4 months, and I can't understand this:
There is two code:
Code 1.
late Future _placesFuture;
@override
void initState() {
_placesFuture = ref.read(placesProvider.notifier).getData();
super.initState();
}
FutureBuilder(
future: _placesFuture,
builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: places.isEmpty
? const Center(
child: Text('No item here.'),
)
: ListView.builder(
itemBuilder: (context, index) => PlaceItem(places[index]),
itemCount: places.length,
),
)
Code 2.
FutureBuilder(
future: ref.read(placesProvider.notifier).getData(),
builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: places.isEmpty
? const Center(
child: Text('No item here.'),
)
: ListView.builder(
itemBuilder: (context, index) => PlaceItem(places[index]),
itemCount: places.length,
),
)
They only Code 1 can work, Code 2 will always been ConnectionState.waiting
.
I don't know why I must initState
_placesFuture first and then use _placesFuture
in future.
Why could not just put ref.read(placesProvider.notifier).getData()
in the future?
Thanks πππ
I'm try to Google and ask to ChatGPT, but I still feel confused.
The whole code:
// places_provider.dart
import 'dart:io';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path/path.dart' as path;
import 'package:sqflite/sqflite.dart' as sql;
import '../models/place.dart';
Future<sql.Database> _getDb() async {
final dbPath = await sql.getDatabasesPath();
final db = await sql.openDatabase(
path.join(dbPath, 'places.db'),
onCreate: (db, version) {
return db.execute('CREATE TABLE places(id TEXT PRIMARY KEY, title TEXT, image TEXT, lat REAL, lng REAL, address TEXT)');
},
version: 1,
);
return db;
}
class PlacesNotifier extends StateNotifier<List<Place>> {
PlacesNotifier() : super([]);
Future<void> removePlace(Place place) async {
final db = await _getDb();
db.delete('places', where: 'id = ?', whereArgs: [place.id]);
if (state.contains(place)) {
state = state.where((element) => element.id != place.id).toList();
}
}
Future<void> addPlace(Place place) async {
final db = await _getDb();
db.insert('places', {
'id': place.id,
'title': place.title,
'image': place.image.path,
'lat': place.location.lat,
'lng': place.location.lng,
'address': place.location.address,
});
state = [place, ...state];
}
Future<void> getData() async {
final db = await _getDb();
final data = await db.query('places');
final p = data
.map(
(e) => Place(
id: e['id'] as String,
title: e['title'] as String,
image: File(e['image'] as String),
location: PlaceLocation(
lat: e['lat'] as double,
lng: e['lng'] as double,
address: e['address'] as String,
),
),
)
.toList();
state = p;
}
}
final placesProvider = StateNotifierProvider<PlacesNotifier, List<Place>>(
(ref) {
return PlacesNotifier();
},
);
// places_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/places_provider.dart';
import '../models/place.dart';
import '../widgets/place_item.dart';
import './new_place_screen.dart';
class PlacesScreen extends ConsumerStatefulWidget {
const PlacesScreen({super.key});
@override
ConsumerState<PlacesScreen> createState() => _PlacesScreenState();
}
class _PlacesScreenState extends ConsumerState<PlacesScreen> {
late Future _placesFuture;
@override
void initState() {
_placesFuture = ref.read(placesProvider.notifier).getData();
super.initState();
}
@override
Widget build(BuildContext context) {
final places = ref.watch(placesProvider);
return Scaffold(
appBar: AppBar(
title: const Text('ζηζζη§ε―εΊε°'),
actions: [
IconButton(
onPressed: () async {
final p = await Navigator.of(context).push<Place>(MaterialPageRoute(
builder: (context) => const NewPlaceScreen(),
));
ref.read(placesProvider.notifier).addPlace(p!);
},
icon: const Icon(Icons.add))
],
),
body: FutureBuilder(
future: _placesFuture,
builder: (context, snapshot) => snapshot.connectionState == ConnectionState.waiting
? const Center(
child: CircularProgressIndicator(),
)
: places.isEmpty
? const Center(
child: Text('ε°ζͺζ·»ε δ»»δ½ε°ι»γ'),
)
: ListView.builder(
itemBuilder: (context, index) => PlaceItem(places[index]),
itemCount: places.length,
),
),
);
}
}
or view in GitHub.
In general, you should always use the style like Code 1.
Because if you put the request in FutureBuilder
, it will make another call to the database Whenever the Widget rebuild.
In your Code 2, because ref.watch(placesProvider);
is called with the ref of PlacesScreen
, PlacesScreen
will rebuild whenever placesProvider
updates, and calling ref.read(placesProvider.notifier).getData()
in FutureBuilder
will cause a delayed update placesProvider
and trigger a rebuild of PlacesScreen
which also rebuild FutureBuilder
and call ref.read(placesProvider.notifier).getData();
again.