I have a MainPage
, where I have a ListView
of my rooms. The ListView uses the response from my getUserRooms
service. Everything works fine; I can’t understand why The ListView
doesn’t update when my createRoom
request gets called, which is in the same service as the getUserRooms
functionality. I’m managing the state using Riverpod
Here are my HTTP requests:
class RoomService {
RoomRoutes roomRoutes = RoomRoutes();
final _preferencesService = SharedPreferences();
Future<List> createRoom(RoomModelRequest postBody) async {
if (postBody.roomPassword!.trim().isEmpty) {
postBody.roomPassword = null;
}
if (postBody.roomName!.trim().isEmpty) {
postBody.roomName = null;
}
try {
final userData = await _preferencesService.getPreferences();
final response = await http.post(Uri.parse(roomRoutes.roomsURL),
headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token ${userData.token}'
},
body: jsonEncode(postBody.toJson()));
switch (response.statusCode) {
case 201:
final data = jsonDecode(response.body);
return [response.statusCode, data["Success"]];
default:
throw Exception(response.reasonPhrase);
}
} on SocketException {
throw Exception("No internet connection");
} on TimeoutException catch (e) {
throw Exception("Connection timeout: ${e.message} ");
} on Exception {
rethrow;
}
}
Future<List<RoomModelResponse>> getUserRooms() async {
try {
final userData = await _preferencesService.getPreferences();
final response =
await http.get(Uri.parse(roomRoutes.extendedRoomsURL), headers: {
'Content-Type': 'application/json; charset=UTF-8',
'Authorization': 'Token ${userData.token}'
});
switch (response.statusCode) {
case 200:
Iterable json = jsonDecode(response.body);
return json.map((room) => RoomModelResponse.fromJson(room)).toList();
default:
throw Exception(response.reasonPhrase);
}
} on SocketException {
throw Exception("No internet connection");
} on TimeoutException catch (e) {
throw Exception("Connection timeout: ${e.message} ");
} on Exception {
rethrow;
}
}
}
Here in the same file, I have my room provider:
final roomProvider = Provider<RoomService>((ref) => RoomService());
And I’m watching the state of that provider, where on update the getUserRooms is returned.
final roomDataProvider = FutureProvider<List<RoomModelResponse>>((ref) async {
return ref.watch(roomProvider).getUserRooms();
});
In my MainPage
that is how I get the response data
class MainPage extends ConsumerWidget {
const MainPage({super.key});
@override
Widget build(BuildContext context, ref) {
// provider
final data = ref.watch(roomDataProvider);
final roomIDController = TextEditingController();
final uniqueIDRoom = TextEditingController();
// UI screen size
final size = MediaQuery.of(context).size;
double deviceWidth = size.width;
double deviceHeight = size.height;
return Scaffold(
backgroundColor: bluePrimary,
body: data.when(
data: (data) {
List<RoomModelResponse> roomList =
data.map((room) => room).toList();
return SafeArea(
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 36, vertical: 16),
child: Column(
//crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
iconSize: deviceWidth * 0.09,
icon: const Icon(Icons.person_outline,
color: orangePrimary),
onPressed: () {},
),
IconButton(
icon: const Icon(
Icons.add,
color: orangePrimary,
),
iconSize: deviceWidth * 0.09,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
const CreateRoomScreen()),
);
},
)
],
),
SizedBox(height: deviceHeight * 0.04),
Align(
alignment: Alignment.centerLeft,
child: Text("W",
style: TextStyle(
fontFamily: 'Chalet',
fontSize: deviceWidth * 0.12,
color: orangePrimary,
fontWeight: FontWeight.w300,
height: deviceHeight * 0.001)),
),
Align(
alignment: Alignment.centerLeft,
child: Text("W",
style: TextStyle(
fontFamily: 'Chalet',
fontSize: deviceWidth * 0.12,
color: whitePrimary,
fontWeight: FontWeight.w300,
height: deviceHeight * 0.001)),
),
Align(
alignment: Alignment.centerLeft,
child: Text("M",
style: TextStyle(
fontFamily: 'Chalet',
fontSize: deviceWidth * 0.12,
color: orangePrimary,
fontWeight: FontWeight.w300,
height: deviceHeight * 0.001)),
),
SizedBox(height: deviceHeight * 0.04),
Align(
alignment: Alignment.centerLeft,
child: Text("Join room",
style: TextStyle(
fontFamily: 'Chalet',
fontSize: deviceWidth * 0.07,
color: whitePrimary,
fontWeight: FontWeight.w100,
height: deviceHeight * 0.001)),
),
SizedBox(height: deviceHeight * 0.008),
// email textField
SizedBox(
width: MediaQuery.of(context).size.width * 0.85,
child: TextField(
controller: roomIDController,
decoration: InputDecoration(
filled: true,
fillColor: whitePrimary,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide.none),
hintText: 'Enter room ID to join it',
hintStyle: const TextStyle(
color: Color.fromARGB(255, 174, 173, 173))),
),
),
SizedBox(height: deviceHeight * 0.016),
Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton(
backgroundColor: orangePrimary,
child: const Icon(Icons.arrow_forward_ios_rounded,
color: whitePrimary),
onPressed: () {},
),
),
SizedBox(height: deviceHeight * 0.020),
Align(
alignment: Alignment.centerLeft,
child: Text("My rooms",
style: TextStyle(
fontFamily: 'Chalet',
fontSize: deviceWidth * 0.07,
color: whitePrimary,
fontWeight: FontWeight.w100,
height: deviceHeight * 0.001)),
),
SizedBox(height: deviceHeight * 0.014),
// Display horizontal scroll rooms
Align(
alignment: Alignment.centerLeft,
child: SizedBox(
width: deviceWidth,
child: SizedBox(
height: deviceWidth * 0.56,
width: deviceWidth * 0.42,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: roomList.length,
itemBuilder: (context, index) {
return Stack(children: [
Container(
height: deviceWidth * 0.8,
width: deviceWidth * 0.42,
margin: const EdgeInsets.symmetric(
horizontal: 3),
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.circular(10)),
),
InkWell(
child: Container(
height: deviceWidth * 0.4,
width: deviceWidth * 0.42,
margin: const EdgeInsets.symmetric(
horizontal: 3),
decoration: BoxDecoration(
color: orangePrimary,
borderRadius:
BorderRadius.circular(10),
boxShadow: const [
BoxShadow(
color: Colors.black,
offset: Offset(0, 5),
blurRadius: 10)
]),
child: Image.asset(
"assets/Logo.png"),
),
onTap: () {},
),
Positioned(
bottom: 35,
left: 12,
child: Text(roomList[index].roomName)),
Positioned(
bottom: 15,
left: 12,
child: GestureDetector(
onTap: () {
uniqueIDRoom.text =
roomList[index].uniqueID;
Clipboard.setData(ClipboardData(
text: uniqueIDRoom.text));
const snackBar = SnackBar(
content: Text("Copied room ID"),
);
// Find the ScaffoldMessenger in the widget tree
// and use it to show a SnackBar.
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
},
child: Text(
"ID: ${roomList[index].uniqueID}",
style: const TextStyle(
fontWeight: FontWeight.bold)),
))
]);
},
),
),
),
)
]),
));
},
error: (err, s) => Text(err.toString()),
loading: () => const Center(
child: CircularProgressIndicator(),
)));
}
}
Then, to create the room, I navigate the user to another screen perform a HTTP request, which, if successful returns the user to the MainPage again, and I expect the rooms to be updated, including the new room, also.
2
Answers
Ensure that the state is being updated:
ref.read(roomDataProvider.notifier).refresh();
Use
StateNotifierProvider
instead ofFutureProvider
for better control:final roomStateProvider = StateNotifierProvider<RoomNotifier, List>((ref) {
return RoomNotifier(ref.read);
});
Use
Consumer
widget to watch for changes:Consumer(
builder: (context, ref, child) {
final rooms = ref.watch(roomStateProvider);
// Build UI using rooms
},
)
Check if the
roomDataProvider
is being called on screen re-entry:// In your MainPage build method
final data = ref.watch(roomDataProvider);
if (data.isEmpty) {
// Fetch data again or trigger a refresh
ref.refresh(roomDataProvider);
}
Your
RoomService
is not implemented to notify changes to listeners of the provider. You shall modify you code such thatRoomService
extends aNotifier
class, in your case it should beAsyncNotifier
.It is key that you identify and clearly define what the state returned by your provider is, and implement your code accordingly (maybe the list of rooms in your case??)
Then when that state is modified, for example when getting data from the
createRoom
orgetUserRooms
you shall assign that new data object to thestate
variable of the Riverpod notifier, which will notify anyConsumer
widgets which are "watching" theroomServiceProvider
.Your use case is almost exactly what the official Riverpod documentation explains in its example for To-do lists. You should take a look at it.