Question #177MediumFlutter Basics

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?

#flutter#widget#state#bloc#riverpod#native

Answer

Overview

Yes, you can develop Flutter apps without external state management packages by using native Flutter techniques:

text
setState
,
text
InheritedWidget
,
text
ValueNotifier
, and
text
StreamBuilder
.


Method 1: setState (Simplest)

For simple, local state in a single widget.

Example: Counter App

dart
class 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

dart
class 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

dart
import '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

dart
import '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

MethodComplexityUse Case
setStateSimpleLocal state (single widget)
InheritedWidgetModerateShare state down the tree
ValueNotifierSimpleSingle observable value
StreamBuilderModerateAsync data (API, WebSocket)
ChangeNotifierModerateMultiple listeners

When to Use Each

setState

✅ Simple counters, toggles, form inputs

dart
setState(() {
  _isChecked = !_isChecked;
});

InheritedWidget

✅ Theme, localization, auth state (shared across app)

dart
final theme = AppTheme.of(context);

ValueNotifier

✅ Observable single values (search query, selected item)

dart
ValueListenableBuilder(
  valueListenable: searchQuery,
  builder: (context, query, child) => Text(query),
)

StreamBuilder

✅ Real-time data (chat messages, live updates)

dart
StreamBuilder<List<Message>>(
  stream: messagesStream,
  builder: (context, snapshot) => ListView(...),
)

ChangeNotifier

✅ Complex state with multiple properties (cart, user profile)

dart
AnimatedBuilder(
  animation: cart,
  builder: (context, child) => Text('${cart.items.length} items'),
)

Full App Example (No External State Management)

dart
import '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

MethodBest ForComplexity
setStateLocal stateEasy
InheritedWidgetApp-wide stateModerate
ValueNotifierObservable valuesEasy
StreamBuilderAsync/real-time dataModerate
ChangeNotifierComplex stateModerate

Key Takeaway: You can build full Flutter apps without BLoC, Riverpod, or Provider using native Flutter state management techniques.

Learn more: Flutter State Management