Without statemanagement/BLOC,riverpod like statemanagement not allow need to use native flutter any possible way to update the state of the widget or how we can develop the app without state management?
Answer
Overview
Yes, you can develop Flutter apps without external state management packages by using native Flutter techniques:
setStateInheritedWidgetValueNotifierStreamBuilderMethod 1: setState (Simplest)
For simple, local state in a single widget.
Example: Counter App
dartclass CounterScreen extends StatefulWidget { _CounterScreenState createState() => _CounterScreenState(); } class _CounterScreenState extends State<CounterScreen> { int _counter = 0; void _increment() { setState(() { _counter++; }); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Counter')), body: Center( child: Text('Count: $_counter', style: TextStyle(fontSize: 32)), ), floatingActionButton: FloatingActionButton( onPressed: _increment, child: Icon(Icons.add), ), ); } }
Pros:
- ✅ Simple
- ✅ No dependencies
- ✅ Good for local state
Cons:
- ❌ Only works within one widget
- ❌ Cannot share state with other widgets
Method 2: InheritedWidget (Share State Down the Tree)
Share state with descendant widgets.
Example: Theme State
dart// 1. Create InheritedWidget class AppState extends InheritedWidget { final bool isDarkMode; final Function toggleTheme; AppState({ required this.isDarkMode, required this.toggleTheme, required Widget child, }) : super(child: child); static AppState? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<AppState>(); } bool updateShouldNotify(AppState oldWidget) { return oldWidget.isDarkMode != isDarkMode; } } // 2. Wrap app with StatefulWidget + InheritedWidget class MyApp extends StatefulWidget { _MyAppState createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { bool _isDarkMode = false; void _toggleTheme() { setState(() { _isDarkMode = !_isDarkMode; }); } Widget build(BuildContext context) { return AppState( isDarkMode: _isDarkMode, toggleTheme: _toggleTheme, child: MaterialApp( theme: _isDarkMode ? ThemeData.dark() : ThemeData.light(), home: HomeScreen(), ), ); } } // 3. Access state in child widgets class HomeScreen extends StatelessWidget { Widget build(BuildContext context) { final appState = AppState.of(context)!; return Scaffold( appBar: AppBar(title: Text('Home')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Dark Mode: ${appState.isDarkMode}'), ElevatedButton( onPressed: () => appState.toggleTheme(), child: Text('Toggle Theme'), ), ], ), ), ); } }
Pros:
- ✅ Share state with descendants
- ✅ No external dependencies
- ✅ Efficient (only rebuilds dependents)
Cons:
- ⚠️ More boilerplate than packages
- ⚠️ Only works down the tree (not up)
Method 3: ValueNotifier + ValueListenableBuilder
For simple observable state.
Example: Todo List
dartclass TodoScreen extends StatefulWidget { _TodoScreenState createState() => _TodoScreenState(); } class _TodoScreenState extends State<TodoScreen> { final ValueNotifier<List<String>> _todos = ValueNotifier([]); void _addTodo(String todo) { _todos.value = [..._todos.value, todo]; } void dispose() { _todos.dispose(); super.dispose(); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Todo List')), body: Column( children: [ TextField( onSubmitted: _addTodo, decoration: InputDecoration(labelText: 'Add Todo'), ), Expanded( child: ValueListenableBuilder<List<String>>( valueListenable: _todos, builder: (context, todos, child) { return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { return ListTile(title: Text(todos[index])); }, ); }, ), ), ], ), ); } }
Pros:
- ✅ Simple observable pattern
- ✅ No external dependencies
- ✅ Efficient (only rebuilds listeners)
Cons:
- ⚠️ Limited to single values
- ⚠️ Not suitable for complex state
Method 4: StreamBuilder (Reactive Streams)
For asynchronous state updates.
Example: Real-Time Counter
dartimport 'dart:async'; class CounterScreen extends StatefulWidget { _CounterScreenState createState() => _CounterScreenState(); } class _CounterScreenState extends State<CounterScreen> { final StreamController<int> _counterController = StreamController<int>(); int _counter = 0; void _increment() { _counter++; _counterController.add(_counter); } void dispose() { _counterController.close(); super.dispose(); } Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Stream Counter')), body: Center( child: StreamBuilder<int>( stream: _counterController.stream, initialData: 0, builder: (context, snapshot) { return Text('Count: ${snapshot.data}', style: TextStyle(fontSize: 32)); }, ), ), floatingActionButton: FloatingActionButton( onPressed: _increment, child: Icon(Icons.add), ), ); } }
Pros:
- ✅ Reactive updates
- ✅ No external dependencies
- ✅ Good for async data (API, WebSocket)
Cons:
- ⚠️ More complex than text
setState - ⚠️ Must manually manage StreamController
Method 5: ChangeNotifier + AnimatedBuilder
For more complex state with multiple listeners.
Example: Shopping Cart
dartimport 'package:flutter/foundation.dart'; class Cart extends ChangeNotifier { List<String> _items = []; List<String> get items => _items; void addItem(String item) { _items.add(item); notifyListeners(); // Notify listeners } void removeItem(String item) { _items.remove(item); notifyListeners(); } } class CartScreen extends StatefulWidget { _CartScreenState createState() => _CartScreenState(); } class _CartScreenState extends State<CartScreen> { final Cart _cart = Cart(); Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Cart')), body: Column( children: [ ElevatedButton( onPressed: () => _cart.addItem('Item ${_cart.items.length + 1}'), child: Text('Add Item'), ), Expanded( child: AnimatedBuilder( animation: _cart, builder: (context, child) { return ListView.builder( itemCount: _cart.items.length, itemBuilder: (context, index) { return ListTile( title: Text(_cart.items[index]), trailing: IconButton( icon: Icon(Icons.delete), onPressed: () => _cart.removeItem(_cart.items[index]), ), ); }, ); }, ), ), ], ), ); } }
Pros:
- ✅ Multiple listeners
- ✅ No external dependencies
- ✅ Flutter SDK built-in
Cons:
- ⚠️ Less structured than BLoC/Riverpod
Comparison: Native Flutter State Management
| Method | Complexity | Use Case |
|---|---|---|
| setState | Simple | Local state (single widget) |
| InheritedWidget | Moderate | Share state down the tree |
| ValueNotifier | Simple | Single observable value |
| StreamBuilder | Moderate | Async data (API, WebSocket) |
| ChangeNotifier | Moderate | Multiple listeners |
When to Use Each
setState
✅ Simple counters, toggles, form inputs
dartsetState(() { _isChecked = !_isChecked; });
InheritedWidget
✅ Theme, localization, auth state (shared across app)
dartfinal theme = AppTheme.of(context);
ValueNotifier
✅ Observable single values (search query, selected item)
dartValueListenableBuilder( valueListenable: searchQuery, builder: (context, query, child) => Text(query), )
StreamBuilder
✅ Real-time data (chat messages, live updates)
dartStreamBuilder<List<Message>>( stream: messagesStream, builder: (context, snapshot) => ListView(...), )
ChangeNotifier
✅ Complex state with multiple properties (cart, user profile)
dartAnimatedBuilder( animation: cart, builder: (context, child) => Text('${cart.items.length} items'), )
Full App Example (No External State Management)
dartimport 'package:flutter/material.dart'; void main() => runApp(MyApp()); // InheritedWidget for app-wide state class AppState extends InheritedWidget { final ValueNotifier<List<String>> todos; AppState({required this.todos, required Widget child}) : super(child: child); static AppState of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<AppState>()!; } bool updateShouldNotify(AppState oldWidget) => false; } class MyApp extends StatelessWidget { final ValueNotifier<List<String>> todos = ValueNotifier([]); Widget build(BuildContext context) { return AppState( todos: todos, child: MaterialApp( home: TodoScreen(), ), ); } } class TodoScreen extends StatelessWidget { final TextEditingController _controller = TextEditingController(); Widget build(BuildContext context) { final appState = AppState.of(context); return Scaffold( appBar: AppBar(title: Text('Todos')), body: Column( children: [ Padding( padding: EdgeInsets.all(16), child: TextField( controller: _controller, onSubmitted: (value) { if (value.isNotEmpty) { appState.todos.value = [...appState.todos.value, value]; _controller.clear(); } }, decoration: InputDecoration(labelText: 'Add Todo'), ), ), Expanded( child: ValueListenableBuilder<List<String>>( valueListenable: appState.todos, builder: (context, todos, child) { return ListView.builder( itemCount: todos.length, itemBuilder: (context, index) { return ListTile( title: Text(todos[index]), trailing: IconButton( icon: Icon(Icons.delete), onPressed: () { final newTodos = List<String>.from(todos); newTodos.removeAt(index); appState.todos.value = newTodos; }, ), ); }, ); }, ), ), ], ), ); } }
Best Practices
dart// ✅ Use setState for local state setState(() { _counter++; }); // ✅ Use InheritedWidget for app-wide state final appState = AppState.of(context); // ✅ Dispose ValueNotifier and StreamController void dispose() { _notifier.dispose(); _controller.close(); super.dispose(); } // ❌ Don't overuse setState (causes unnecessary rebuilds) // Extract StatefulWidgets for isolated state // ❌ Don't use InheritedWidget for frequently changing state // Use ValueNotifier or ChangeNotifier instead
Summary
| Method | Best For | Complexity |
|---|---|---|
| setState | Local state | Easy |
| InheritedWidget | App-wide state | Moderate |
| ValueNotifier | Observable values | Easy |
| StreamBuilder | Async/real-time data | Moderate |
| ChangeNotifier | Complex state | Moderate |
Key Takeaway: You can build full Flutter apps without BLoC, Riverpod, or Provider using native Flutter state management techniques.
Learn more: Flutter State Management