The Provider Screen package is a comprehensive screen module that simplifies the creation of screens in Flutter applications using the Provider state management solution. It provides an easy way to build screens with features like app bars, safe area handling, floating action buttons, and more. One of the notable benefits of this package is improved readability and simplified state management.
- 🔑 Simplified screen creation with the BaseScreen class
- 🚀 Boost your productivity
- 📚 Enhance code readability
- 🛠 Easy customization for improved maintainability
- 💡 Convenient usage of context within the view model for better programming experience
- ⚙️ Effective management of the Provider's lifecycle for seamless integration
To use the Easy Isolate Mixin package in your Flutter project, follow these steps:
- Depend on it
Add the following line to your project's pubspec.yaml file under the dependencies section:
your pubspec.yaml
file:
dependencies:
provider_screen: ^1.0.0
- Install it
Run the following command in your terminal or command prompt:
$ flutter pub get
- Import it
Add the following import statement to your Dart code:
import 'package:provider_screen/provider_screen.dart';
- Prepare
BaseViewModel
class CustomViewModel extends BaseViewModel {
final String userName = 'James';
}
Create a BaseViewModel class that extends ChangeNotifier
. This class will work as a controller for your screen. It provides methods like notifyListeners()
to update your widget.
- Extend your screen with the
BaseScreen
class and provide aBaseViewModel
as the type
class CustomScreen extends BaseScreen<CustomViewModel> {
const CustomScreen({Key? key}) : super(key: key);
@override
Widget buildScreen(BuildContext context) {
return PlaceHolder();
}
}
Extend your screen widget with the BaseScreen
class and provide the BaseViewModel
type as a generic parameter. Override the buildScreen
method to define the layout of your screen.
- Pass the ViewModel instance to the
createViewModel
method
class CustomScreen extends BaseScreen<CustomViewModel> {
const CustomScreen({Key? key}) : super(key: key);
@override
Widget buildScreen(BuildContext context) {
return PlaceHolder();
}
@override
CustomViewModel createViewModel(BuildContext context) => CustomViewModel();
}
Override the createViewModel
method and return an instance of your CustomViewModel class. This method is responsible for creating and providing the view model instance to the screen.
[NOTE]
If you are familiar with theChangeNotifierProvider
from the provider package, this step is similar to setting up the ChangeNotifierProvider with the specified view model.
If you are using a Service Locator
package like get_it
, you can provide the view model instance as shown in the following code:
@override
CustomViewModel createViewModel(BuildContext context) => GetIt.I<CustomViewModel>();
- Access the ViewModel's properties and methods
You can access the properties and methods of the view model by using the vm(context
) format as a reference. For example:
class CustomScreen extends BaseScreen<CustomViewModel> {
const CustomScreen({Key? key}) : super(key: key);
@override
Widget buildScreen(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
child: Text(vm(contedxt).userName), // just call `vm(context).something`
);
}
@override
CustomViewModel createViewModel(BuildContext context) => CustomViewModel();
}
You can use the vm(context) method to access the view model's properties and methods without listening to changes. If you want to listen to changes, you can use vmW(context)
instead. Additionally, there is a vmS
method available to listen to only a specific part of the view model's data.
- Customize your screen by overriding the optional methods and properties provided by the BaseScreen class
class CustomScreen extends BaseScreen<CustomController> {
const CustomScreen({Key? key}) : super(key: key);
@override
PreferredSizeWidget? buildAppBar(BuildContext context) {
// Custom app bar implementation
return AppBar(
title: const Text('Custom Screen'),
);
}
@override
Widget buildScreen(BuildContext context) {
// Custom screen layout
return Container(
padding: EdgeInsets.all(20),
child: Text(vm(contedxt).userName),
);
}
@override
bool get setBottomSafeArea => false;
@override
Color? get screenBackgroundColor => Colors.white;
@override
CustomViewModel createViewModel(BuildContext context) => CustomViewModel();
// Override other optional methods and properties as needed
}
In the Provider package, there are three BuildContext extensions
for reading a value:
context.read<T>()
, which returnsT
without listening to itcontext.watch<T>()
, which makes the widget listen to changes onT
context.select<T, R>(R cb(T value))
, which allows a widget to listen to only a small part ofT
.
In the Provider Screen package, there are shortened ways to access these features:
T vm(BuildContext context)
, which returnsT
without listening to itT vmW(BuildContext context)
which makes the widget listen to changes onT
S vmS<S>(BuildContext context, S Function(T) selector)
which allows a widget to listen to only a small part ofT
Here is an example to compare the usage:
// Access data without listening to it
Text(context.read<CounterViewModel>().userName),
Text(vm(context).userName),
// Widget listens to changes on `T`
Text(context.watch<CounterViewModel>().userName),
Text(vmW(context).userName),
// Widget listens to only a small part of `T`
Text(context.select<CounterViewModel, String>((value) => value.userName)),
Text(vmS(context, (value) => value.userName)),
You can use these methods based on your preference and needs.
The BaseViewModel
class extends ChangeNotifier
and provides methods for updating the widget and managing the lifecycle
of the view model. Here is an example usage:
class CounterViewModel extends BaseViewModel {
int count = 0;
void increaseCount() {
count++;
notifyListeners();
}
// Other methods and properties
}
The BaseViewModel class includes a notifyListeners()
method, which you can use to update your widget when the data changes. It also provides onInit()
and onDispose()
methods, which you can override to perform initialization and cleanup tasks respectively.
Here is an example of how you can use the BaseViewModel class:
class CounterViewModel extends BaseViewModel {
int count = 0;
void increaseCount() {
count++;
notifyListeners();
}
@override
void onInit() {
super.onInit();
// Perform initialization tasks here
}
@override
void onDispose() {
super.onDispose();
// Perform cleanup tasks here
}
}
In the example above, the increaseCount()
method updates the count property and notifies the listeners. The onInit()
method is called when the view model is initialized, and the onDispose()
method is called when the view model is disposed of.
You can also access the screen's context
in the view model. This can make your programming experience much better, especially when you need to show dialogs
or navigate
to other screens:
class CounterViewModel extends BaseViewModel {
int count = 0;
void showAlertMessage() {
showDialog(
context: context,
builder: (_) {
return AlertDialog(
title: const Text('Alert'),
content: const Text('This is an alert message.'),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('OK'),
),
],
);
},
);
}
}
In the example above, the showAlertMessage()
method shows an alert dialog using the screen's context.
You can add an app bar to your screen by overriding the buildAppBar method.
class CounterScreen extends BaseScreen<CounterViewModel> {
const CounterScreen({Key? key}) : super(key: key);
@override
PreferredSizeWidget? buildAppBar(BuildContext context) {
return AppBar(
title: const Text('Counter App'),
);
}
@override
Widget buildScreen(BuildContext context) {
return const Placeholder();
}
@override
CounterViewModel createViewModel(BuildContext context) => CounterViewModel();
}
Parameter | Default | Description |
---|---|---|
buildAppBar | null | customizes the app bar widget |
To control the safe area behavior of your screen, you can override the following properties.
class CounterScreen extends BaseScreen<CounterViewModel> {
const CounterScreen({Key? key}) : super(key: key);
@override
bool get setBottomSafeArea => false;
@override
PreferredSizeWidget? buildAppBar(BuildContext context) {
return AppBar(
title: const Text('Counter App'),
);
}
@override
Widget buildScreen(BuildContext context) {
return const Placeholder();
}
@override
CounterViewModel createViewModel(BuildContext context) => CounterViewModel();
}
Parameter | Type | Default | Description |
---|---|---|---|
wrapWithSafeArea | bool | true | wrap screen content with SafeArea |
setTopSafeArea | bool | true | consider top safe area. |
setBottomSafeArea | bool | true | consider bottom safe area. |
You can customize the screen's background color and unsafe area color by overriding the following properties
class CounterScreen extends BaseScreen<CounterViewModel> {
const CounterScreen({Key? key}) : super(key: key);
@override
bool get setBottomSafeArea => false;
@override
Color? get screenBackgroundColor => Colors.green;
@override
Color? get unSafeAreaColor => Colors.amber;
@override
PreferredSizeWidget? buildAppBar(BuildContext context) {
return AppBar(
title: const Text('Counter App'),
);
}
@override
Widget buildScreen(BuildContext context) {
return const Placeholder();
}
@override
CounterViewModel createViewModel(BuildContext context) => CounterViewModel();
}
To set a default color for screenBackgroundColor
and unSafeAreaColor
across your app, you can customize the theme in
your MaterialApp widget:
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
// customize it
scaffoldBackgroundColor: Colors.white,
unselectedWidgetColor: Colors.blue,
),
title: 'Provider Screen',
initialBinding: CounterBinding(),
home: const CounterScreen(),
);
}
}
By modifying the theme's scaffoldBackgroundColor
and unselectedWidgetColor
, you can set the default colors for
screenBackgroundColor and unSafeAreaColor respectively.
Parameter | Type | Default | Description |
---|---|---|---|
screenBackgroundColor | Color? | Theme.of(context).scaffoldBackgroundColor | sets the background color of the screen |
unSafeAreaColor | Color? | Theme.of(context).unselectedWidgetColor | sets the color of the unsafe area. |
Create the Floating Action Button widget in your screen.
class CounterScreen extends BaseScreen<CounterViewModel> {
const CounterScreen({Key? key}) : super(key: key);
@override
bool get setBottomSafeArea => false;
@override
Color? get screenBackgroundColor => Colors.green;
@override
Color? get unSafeAreaColor => Colors.amber;
@override
Widget? get buildFloatingActionButton =>
FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
);
@override
FloatingActionButtonLocation? get floatingActionButtonLocation => FloatingActionButtonLocation.startFloat;
@override
PreferredSizeWidget? buildAppBar(BuildContext context) {
return AppBar(
title: const Text('Counter App'),
);
}
@override
Widget buildScreen(BuildContext context) {
return const Placeholder();
}
@override
CounterViewModel createViewModel(BuildContext context) => CounterViewModel();
}
Parameter | Type | Default | Description |
---|---|---|---|
buildFloatingActionButton | Widget? | null | customizes the floating action button widget |
floatingActionButtonLocation | Widget? | null | sets the position of the floating action button. |
You can access the properties and methods of the controller by using vm(context)
format as a reference. In this
case, vm
stands
for "view model," which is an abbreviation commonly used to refer to the associated view model ChangeNotifier
class CounterViewModel extends BaseViewModel {
int count = 0;
void increaseCount() {
count++;
notifyListeners();
}
}
class CounterScreen extends BaseScreen<CounterViewModel> {
const CounterScreen({Key? key}) : super(key: key);
@override
Widget buildScreen(BuildContext context) {
return Center(
child: Text(
'${vmW(context).count}',
style: Theme
.of(context)
.textTheme
.headlineLarge,
),
);
}
@override
bool get setBottomSafeArea => false;
@override
Color? get screenBackgroundColor => Colors.green;
@override
Color? get unSafeAreaColor => Colors.amber;
@override
Widget? get buildFloatingActionButton =>
FloatingActionButton(
onPressed: vm(context).increaseCount,
child: const Icon(Icons.add),
);
@override
FloatingActionButtonLocation? get floatingActionButtonLocation =>
FloatingActionButtonLocation.startFloat;
@override
PreferredSizeWidget? buildAppBar(BuildContext context) {
return AppBar(
title: const Text('Counter App'),
);
}
@override
CounterViewModel createViewModel(BuildContext context) => CounterViewModel();
}
Customize the bottom navigation bar widget in your screen.
class CounterScreen extends BaseScreen<CounterViewModel> {
const CounterScreen({Key? key}) : super(key: key);
// Skipping previous code...
@override
Widget? buildBottomNavigationBar(BuildContext context) {
return BottomNavigationBar(items: const [
BottomNavigationBarItem(
label: 'home',
icon: Icon(Icons.home),
),
BottomNavigationBarItem(
label: 'user',
icon: Icon(Icons.account_circle),
)
]);
}
@override
Widget buildScreen(BuildContext context) {
return Center(
child: Text(
'${vmW(context).count}',
style: Theme
.of(context)
.textTheme
.headlineLarge,
),
);
}
}
Parameter | Type | Default | Description |
---|---|---|---|
buildBottomNavigationBar | Widget? | null | customizes the bottom navigation bar widget. |
Additionally, there are other available options that can be overridden in your screen class
Parameter | Type | Default | Description |
---|---|---|---|
onWillPopCallback | bool Function()? | null | handles the back button press or pop action |
resizeToAvoidBottomInset | bool | true | enables or disables screen resizing to avoid the bottom |
extendBodyBehindAppBar | bool | false | extends the body behind the app bar. |
If you want to build a simple widget without creating a complete app screen based on Scaffold
and separate the controller and screen layout, you can use BaseView
. However, note that the BaseView widget needs to be nested within BaseScreen
. You don't need to override the createViewModel
method and pass a view model instance.
Here's an example usage:
class CounterScreen extends BaseScreen<CounterViewModel> {
const CounterScreen({Key? key}) : super(key: key);
@override
Widget buildScreen(BuildContext context) {
return const Center(
child: CounterIndicator(),
);
}
// Skipping previous code...
}
// Seperated class witch extends BaseView.
class CounterIndicator extends BaseView<CounterViewModel> {
const CounterIndicator({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(
'${vmW(context).count}',
style: Theme
.of(context)
.textTheme
.headlineLarge,
);
}
}
The CustomIndicator widget extends BaseView and overrides the buildView method to define the layout of the widget. Inside the buildView method, you can access the view model properties and methods using the vmW(context) format.
If you prefer using the GetX state management library, you can check out the getx_screen package. This package offers similar benefits to the provider_screen
package but is designed specifically for GetX users.