I hope someone can help me with this :) I have a flutter app and use the provider package to manage state.
What I want to achieve: When I click on the button and delete an order I want to notify my button and provide the isLoading value. The button should show a loading spinner and lower its opacity when isLoading == true.
What is the current state: When I click on the button the order gets deleted, but neither the opacity get lowered nor the loading spinner gets displayed. Though I have access to isLoading in my button.
This is my OrderService (store):
import ...
class OrderService with ChangeNotifier {
final supabaseClient = Supabase.instance.client;
bool _isLoading = false;
bool get isLoading => _isLoading;
_toggleLoading() {
_isLoading = !_isLoading;
notifyListeners();
}
Future<void> deleteOrderWithId(int id, BuildContext context) async {
try {
_toggleLoading();
await supabaseClient.from('orders').delete().eq('id', id);
} catch (err) {
print(err);
} finally {
_toggleLoading();
}
}
Future<void> createOrderFromOrder(Order order, BuildContext context) async {...}
}
This is my custom button:
import ...
class TrustyButton extends StatelessWidget {
final void Function() onTap;
final String text;
final ButtonType type;
final Icon? icon;
TrustyButton({
super.key,
required this.onTap,
required this.text,
required this.type,
this.icon,
});
final Map<ButtonType, Map<String, Color>> color = {...}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => OrderService(),
child: Consumer<OrderService>(
builder: (context, orderService, _) => GestureDetector(
onTap: () {
onTap();
},
child: Opacity(
opacity: orderService.isLoading ? 0.5 : 1,
child: Container(
height: 56,
decoration: BoxDecoration(
color: color[type]!['backgroundColor']!,
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
boxShadow: [
BoxShadow(
color: const Color(0x15000000),
blurRadius: 10,
spreadRadius: 0,
offset: Offset.fromDirection(2, 2),
),
],
),
child: Center(
child: orderService.isLoading
? const CircularProgressIndicator(
color: Colors.white,
)
: renderButtonContent(),
),
),
),
),
),
);
}
}
You are facing this issue, because you are creating a new instance of the OrderService
inside the TrustyButton
. Instead you must lift the ChangeNotifierProvider<TrustyButton>
, and to use it via a Consumer
inside the TrustyButton
.
Here is a minimal example:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ChangeNotifierProvider(
create: (context) => OrderService(),
builder: (context, _) {
return TrustyButton(
onTap: () => context.read<OrderService>().deleteOrderWithId(),
);
},
),
),
),
);
}
}
class OrderService with ChangeNotifier {
bool _isLoading = false;
bool get isLoading => _isLoading;
void _toggleLoading() {
_isLoading = !_isLoading;
notifyListeners();
}
Future<void> deleteOrderWithId() async {
try {
_toggleLoading();
await Future.delayed(const Duration(seconds: 5));
} catch (err) {
print(err);
} finally {
_toggleLoading();
}
}
}
class TrustyButton extends StatelessWidget {
final void Function() onTap;
TrustyButton({
super.key,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return Consumer<OrderService>(
builder: (context, orderService, _) => GestureDetector(
onTap: onTap,
child: Opacity(
opacity: orderService.isLoading ? 0.5 : 1,
child: Container(
height: 56,
decoration: BoxDecoration(
color: Colors.amberAccent,
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
boxShadow: [
BoxShadow(
color: const Color(0x15000000),
blurRadius: 10,
spreadRadius: 0,
offset: Offset.fromDirection(2, 2),
),
],
),
child: Center(
child: orderService.isLoading
? const CircularProgressIndicator(
color: Colors.white,
)
: Text('Delete'),
),
),
),
),
);
}
}