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 Notifier
class, in your case it should be AsyncNotifier
.
Key concepts: A Provider
is just an utility that (as its name indicates) provides, across your entire app, a certain type
of object. That object is cached and hence the same object is returned. A Notifier
is a special type of Provider
which 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 that NotifierProvider
will be notified of any updates on that state (this is done by invoking the watch
method 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 get
HTTP request, you shall assign that new data object to the state
variable of the Riverpod notifier, which will notify any Consumer
widgets 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.