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