In Flutter, is it okay to use same provider class in different pages to avoid provider code duplication or will this approach have adverse consequences unseen to me? Should I instead go for one provider class per page (meaning code duplication)?
As I was expanding the code of my project, I realized that multiple pages in my project use similar code/widgets for different purposes and that I could use same provider class across these pages to avoid duplication of codes, writing one provider class for each of those pages. But, I am unsure if this can or will have any adverse side-effects that are unseen to me. So, I have the question stated above.
Parts of my code:
Relevant code of one of my provider:
ClickableTabBarSelected? clickableTabBarSelected =
ClickableTabBarSelected.leftTab;
void selectedClickableTabBar(ClickableTabBarSelected ctbs) {
print("#### User selected tab bar: ${ctbs}");
clickableTabBarSelected = ctbs;
notifyListeners();
}
My second provider needs to show result in a custom widget when the result is available. This will be something like:
bool _isResultAvailable = false;
bool get isResultAvailableForUse => _isResultAvailable;
void resetResultAvailableStatusWithoutNotifyListners() {
// Reset status but don't call notifyListeners method.
_isResultAvailable = false;
}
void setResultAvailableForUseStatus() {
_isResultAvailable = true;
notifyListeners();
}
My third provider, is for checking if Admob ad is loaded, and if it is, display a banner ad. The provider has a function initializeBannerHomePage
which sends request to Admob, and sets the provider's boolean variable isBannerHomePageLoaded
as per the listener status and calls notifyListeners().
However, it can lead to unintended consequences depending on how the state is managed.
Understanding the Issue with Shared Providers
Consider a scenario where a bool
property (isActive
) in the Provider determines the color of a button:
isActive
is true
, the button turns blue
.isActive
is false
, the button turns red
.If multiple widgets share the same Provider
instance and one widget updates isActive
, all other widgets listening to the provider will also reflect the change.
Example:
ButtonWidget
in different places in the code and the isActive
bool
is to change the color of each component .isActive
set as true
in one ButtonWidget componet then it will change all the other Widgets that shared the same provider and its property isActive
bool=> Demo Video for this scenario 1: shared_provider_instance
In this case, all widgets that depend on isActive
will change together.
class ResuableWidget extends StatelessWidget {
const ResuableWidget({
super.key,
required this.lable,
required this.isActive,
required this.onStateChange,
required this.activationLable,
});
final void Function() onStateChange;
final String lable;
final bool isActive;
final String activationLable;
double get screenWidth => MediaQuery.sizeOf(context).width;
double get screenHeight => MediaQuery.sizeOf(context).height;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
children: [
SizedBox(
width: screenWidth * .6,
height: screenHeight * .07,
child: MaterialButton(
onPressed: onStateChange,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(25),
),
color: isActive ? Colors.red : Colors.brown,
child: Text(lable),
),
),
if (isActive) ...{
Text(
activationLable,
style:
const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
},
],
),
],
);
}
}
Provider
that will manages isActive
bool
State:
class ReusableProvider extends ChangeNotifier {
bool isActive = false;
void get changeButtonState {
isActive = !isActive;
log("Current Button State :$isActive");
notifyListeners();
}
}
ChangeNotifierProvider
:
class TestSharedProvider extends StatelessWidget {
const TestSharedProvider({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.cyan,
automaticallyImplyLeading: false,
title: const Text("UI with Same Provider"),
centerTitle: true,
),
body: ChangeNotifierProvider(
create: (context) => ReusableProvider(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Consumer<ReusableProvider>(
builder: (context, test1, _) {
return ResuableWidget(
lable: "Test 1",
isActive: test1.isActive,
activationLable: "Provider Instance 1 is Active",
onStateChange: () {
test1.changeButtonState;
log("Button 1 State : ${test1.isActive}");
},
);
},
),
SizedBox(height: 20),
Consumer<ReusableProvider>(
builder: (context, test2, _) {
return ResuableWidget(
lable: "Test 2",
isActive: test2.isActive,
activationLable: "Provider Instance 2 is Active",
onStateChange: () {
test2.changeButtonState;
log("Button 2 State : ${test2.isActive}");
},
);
},
),
],
),
),
);
}
}
Issue: Since both buttons use the same ReusableProvider
instance, clicking one button affects all the widgets that related with isActive
.
=> Demo Video for this scenario 2: non_shared_provider
You have the Main Screen with the reusable widgets but with different Instances of provider ChangenotifierProvider
class TestNonSharedProviderWidget extends StatelessWidget {
const TestNonSharedProviderWidget({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.cyan,
automaticallyImplyLeading: false,
title: const Text("UI with Same Provider"),
centerTitle: true,
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ChangeNotifierProvider(
create: (context) => ReusableProvider(),
child: Consumer<ReusableProvider>(
builder: (context, test1, _) {
return ResuableWidget(
lable: "Test 1",
isActive: test1.isActive,
activationLable: "Provider Instance 1 is Active",
onStateChange: () {
test1.changeButtonState;
log("Button 1 State : ${test1.isActive}");
},
);
},
),
),
SizedBox(height: 20),,
ChangeNotifierProvider(
create: (context) => ReusableProvider(),
child: Consumer<ReusableProvider>(
builder: (context, test2, _) {
return ResuableWidget(
lable: "Test 2",
isActive: test2.isActive,
activationLable: "Provider Instance 2 is Active",
onStateChange: () {
test2.changeButtonState;
log("Button 2 State : ${test2.isActive}");
},
);
},
),
),
],
),
);
}
}
isActive
in one button does not affect the other.Finally:
Really hope this is help you!!