Part 5: Working with Forms and Input Handling

This entry is part 6 of 8 in the series Flutter Development Tutorial (Beginner to Intermediate)

11. Managing Asynchronous Code in Flutter

Flutter uses Dart’s Future and async/await for handling asynchronous tasks such as API calls, file reading, or database access.
11.1 Using Future and async/await

Dart’s Future represents an asynchronous computation that will return a result in the future.

Code Example: Simple Future and async/await

Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 2));
  return 'Data loaded';
}

class AsyncExample extends StatefulWidget {
  @override
  _AsyncExampleState createState() => _AsyncExampleState();
}

class _AsyncExampleState extends State<AsyncExample> {
  late Future<String> data;

  @override
  void initState() {
    super.initState();
    data = fetchData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Async Example')),
      body: FutureBuilder<String>(
        future: data,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return Center(child: CircularProgressIndicator());
          } else if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          } else {
            return Center(child: Text('Result: ${snapshot.data}'));
          }
        },
      ),
    );
  }
}
Best Practices for Asynchronous Code
  1. Use FutureBuilder to display async data in widgets.
  2. Handle errors properly using try-catch or snapshot.hasError.
  3. Keep async logic outside the UI layer for better separation of concerns.

12. State Management in Flutter

State management is a key aspect of building dynamic applications where data changes often. In Flutter, managing state is essential for maintaining data between user interactions, screen navigation, and UI updates. There are different ways to manage state depending on the app’s complexity.


12.1 Stateful Widgets (Basic State Management)

The simplest form of state management in Flutter is using Stateful Widgets. These widgets allow you to update the UI dynamically when state changes.
Example: Stateful Counter App

class CounterExample extends StatefulWidget {
  @override
  _CounterExampleState createState() => _CounterExampleState();
}

class _CounterExampleState extends State<CounterExample> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter Example')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pressed the button this many times:'),
            Text('$_counter', style: Theme.of(context).textTheme.headline4),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
How it Works:
  • The setState() method triggers the widget tree to rebuild whenever the _counter value changes.
  • This is ideal for small, simple applications, but it can become cumbersome in larger apps.

12.2 Lifting State Up

Sometimes, state is shared across multiple widgets. In such cases, “lifting the state up” to a common ancestor widget helps manage the state centrally.


Example: Lifting State Up

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  bool _isActive = false;

  void _toggleActive() {
    setState(() {
      _isActive = !_isActive;
    });
  }

  @override
  Widget build(BuildContext context) {
    return ChildWidget(
      isActive: _isActive,
      onToggle: _toggleActive,
    );
  }
}

class ChildWidget extends StatelessWidget {
  final bool isActive;
  final VoidCallback onToggle;

  ChildWidget({required this.isActive, required this.onToggle});

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: onToggle,
      child: Container(
        color: isActive ? Colors.green : Colors.red,
        child: Text(isActive ? 'Active' : 'Inactive'),
      ),
    );
  }
}
How it Works:
  • The parent widget controls the state (_isActive), and passes it down to the child widget along with a callback to modify the state.
  • This is effective for scenarios where the state needs to be shared between a few widgets.

12.3 Provider (Advanced State Management)

For larger applications, using a state management solution like Provider is more scalable. Provider allows you to efficiently share state across the widget tree and rebuild only the necessary parts of the UI.
Step 1: Add Provider to pubspec.yaml

dependencies:
  provider: ^6.0.0

Step 2: Create a Simple ChangeNotifier Model

class CounterModel with ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    _counter++;
    notifyListeners();
  }
}

Step 3: Provide the Model to the App

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: MyApp(),
    ),
  );
}

Step 4: Use the Model in a Widget

class CounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<CounterModel>(context);

    return Scaffold(
      appBar: AppBar(title: Text('Provider Example')),
      body: Center(
        child: Text('Counter: ${counter.counter}', style: TextStyle(fontSize: 24)),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: counter.increment,
        child: Icon(Icons.add),
      ),
    );
  }
}
Best Practices for State Management:
  • Use setState() for simple, local state management.
  • Use Provider or other solutions (e.g., Riverpod, Bloc) for global state management and more complex applications.
  • Avoid lifting state too much as it can make the widget tree difficult to maintain. Use dedicated state management solutions instead.

Related posts<< Part 4: Navigation and RoutingPart 6: Lists, Grids, and Dynamic Content >>