You could lift up the state of your widgets in your Flutter App and it could become independent with Minimal Code using Provider State Management and Go Router for a navigation.
Any meaningful app has
- Multiple Screens
- Cross Screen State
- Single Screen Only State
You could implement
- Navigating between screens with Go Router.
- Having Cross Screen State (Inter-Feature Communication) – with Global Provider Definition
- Providers only for around single Screen only
Whatever State Management – you will find this common pattern – Have a Screen / Feature / Minimally, but fully funcitonal and independent component. So any of these – will have their own
- directory with:
- views/components
- dependency injection definitions
- local providers, blocs, cubits, Get Services
With this archtecture – you could get rid of great majority of your StatefulWidgets.
Minimal Code of Flutter App
main.dart
The main.dart may be the place for global definitions:
MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => yourGlobalService()..fetchData()), ], child: const MyApp(), )
my_app.dart
My App is the common place to define global settings, bindings, definitions. Here is also the place for the router.
class MyApp extends StatelessWidget { MyApp({super.key}); final _router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => MultiProvider( providers: [ ChangeNotifierProvider(create: (_) => LocalService()..fetchData()), ], child: const FirstScreen(), ), ), GoRoute( path: '/second', builder: (context, state) => const SecondScreen(), ), ], ); // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp.router( routerConfig: _router, ); } }
my_service.dart
import 'package:flutter/widgets.dart';
class MyService extends ChangeNotifier {
bool isLoading = false;
bool success = false; // Tracks whether the network call was successful
Future fetchData() async {
isLoading = true;
success = false; // Reset success before making a new call
notifyListeners();
Future.delayed(
const Duration(seconds: 2),
() {
success = true;
isLoading = false;
notifyListeners();
},
);
// final response = await http
// .get(Uri.parse('https://programtom.com/dev'));
//
// if (response.statusCode == 200) {
// success = true;
// } else {
// success = false;
// }
}
}
Use Consumer
in FirstScreen
:
Here, we refactor FirstScreen
to use Consumer
, which listens for changes in NetworkProvider
‘s state and reacts accordingly.
import 'package:file_sharing_app/services/LoginService.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:go_router/go_router.dart';
class FirstScreen extends StatelessWidget {
const FirstScreen({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('First Screen')),
body: Center(
child: Consumer(
builder: (context, networkProvider, child) {
// Check if the network call was successful and navigate
if (networkProvider.success) {
Future.microtask(() {
if (context.mounted) {
GoRouter.of(context).replace('/second');
}
});
}
return networkProvider.isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: () {
// Trigger the network call
networkProvider.fetchData();
},
child: const Text('Loading ...'),
);
},
),
),
);
}
Second Screen
import 'package:flutter/material.dart'; class SecondScreen extends StatelessWidget { const SecondScreen({super.key}); @override Widget build(BuildContext context) => const Scaffold( body: Text("second"), ); }
Key Points:
Consumer<
: Reactively listens to changes in theMyService
>MyService
state and rebuilds the widget when changes occur. If you want to optimize – use Selector – to specify only one field from the Provider – minimizing the UI rebuilds.- Navigation with
Future.microtask()
: Since Flutter’s build method might be called multiple times during a rebuild, we useFuture.microtask()
to ensure navigation happens after the current frame finishes rendering. - No
StatefulWidget
: This solution eliminates the need forStatefulWidget
, since theConsumer
takes care of reacting to state changes.
Explanation:
Consumer<NetworkProvider>
rebuilds wheneverisLoading
orsuccess
changes.- If
success
istrue
, navigation to the second screen is triggered. - When the button is pressed, the
fetchData()
method is called, and changes in the provider are reflected byConsumer
rebuilding the widget tree.
This approach is simple, efficient, and reactive, eliminating the need for managing state manually via StatefulWidget
.