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.
Your RoomService and roomDataProvider are not implemented to notify changes to listeners of those providers. You shall modify you code such that depending on you case they extend a
Notifierclass, in your case it should beAsyncNotifier.Key concepts: A
Provideris just an utility that (as its name indicates) provides, across your entire app, a certaintypeof object. That object is cached and hence the same object is returned. ANotifieris a special type ofProviderwhich has an internal state property which stores the object to be returned. This state can be updated by implementing methods which assign new instances to the state property. Listeners to thatNotifierProviderwill be notified of any updates on that state (this is done by invoking thewatchmethod in your code wherever you need that state object).It is key that you identify and clearly define what the state returned by your provider is, and implement your code accordingly (maybe in your case it is a list of rooms??)
Then when that state is modified, for example when getting new data when performing a
getHTTP request, you shall assign that new data object to thestatevariable of the Riverpod notifier, which will notify anyConsumerwidgets which are "watching".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.