skip to Main Content

The code below is a simple example of a Flutter widget I want to refactor, to improve both organization and readability.

I want to refactor _incrementCounter and _decrementCounter so that the _MySimpleWidgetState class isn’t so cluttered. I’ve tried extending _MySimpleWidgetState class by moving the _incrementCounter and _decrementCounter functions there, but that made the code less readable.

What am I missing here? I vaguely recall knowing how to do this, but the technique isn’t coming to mind right now. Please forgive me if this is something really basic.


class MySimpleWidget extends StatefulWidget {
  const MySimpleWidget({super.key});

  @override
  State<MySimpleWidget> createState() => _MySimpleWidgetState();
}

class _MySimpleWidgetState extends State<MySimpleWidget> {

  int _counter = 0;

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

  _decrementCounter() {
    setState(() {
      _counter--;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.only(top: 16.0),

          child: Text('Counter Value: $_counter'),
        ),

        TextButton(onPressed: () => _incrementCounter(), 
        child: const Text('Increment Counter')),

        TextButton(onPressed: () => _decrementCounter(), 
        child: const Text('Decrement Counter'))
      ],
    );
  }
}

2

Answers


  1. To make your code more organized and readable, you can separate the logic from the UI by creating a separate class to handle the counting logic (incrementing and decrementing).

    This way, the State class of your widget can focus on the UI, making your code easier to manage.

    Here’s how to do it:

    1. Create a CounterController class: This class will manage the logic for incrementing and decrementing the counter.
    2. Keep the MySimpleWidget and _MySimpleWidgetState focused on the UI: These classes will only handle the user interface.
    3. Pass the CounterController to the widget’s state, maintaining a clear separation between logic and UI.

    Refactored Code:

    class CounterController {
      int _counter = 0;
    
      int get counter => _counter;
    
      void incrementCounter() {
        _counter++;
      }
    
      void decrementCounter() {
        _counter--;
      }
    }
    
    class MySimpleWidget extends StatefulWidget {
      const MySimpleWidget({super.key});
    
      @override
      State<MySimpleWidget> createState() => _MySimpleWidgetState();
    }
    
    class _MySimpleWidgetState extends State<MySimpleWidget> {
      final CounterController _counterController = CounterController();
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            Padding(
              padding: const EdgeInsets.only(top: 16.0),
              child: Text('Counter Value: ${_counterController.counter}'),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  _counterController.incrementCounter();
                });
              },
              child: const Text('Increment Counter'),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  _counterController.decrementCounter();
                });
              },
              child: const Text('Decrement Counter'),
            ),
          ],
        );
      }
    }
    

    Explanation:

    • CounterController Class: This class manages the counting logic, so the widget’s state no longer directly handles it.

    • _MySimpleWidgetState Class: This class is responsible for the UI. It creates an instance of CounterController and uses its methods to update the counter.

    This approach keeps your code modular, separating business logic from the UI, which makes it easier to maintain and scale in the future.

    Login or Signup to reply.
  2. The key idea is to move the logic that doesn’t directly involve UI concerns (in this case, the counter manipulation logic) out of the widget’s state class. We can achieve this by introducing a separate class that handles the counter logic. This approach follows the Single Responsibility Principle, where the _MySimpleWidgetState will focus solely on the UI, and the counter logic will reside in a dedicated class.

    Create a Separate Counter Logic Class: This class will handle the incrementing and decrementing logic. It keeps the state and logic related to the counter separate from the UI, making it easier to maintain.

    Use the Counter Class in the State: The state class will delegate counter manipulation to the newly created counter logic class. The state class will still call setState() to notify Flutter of any changes, but it no longer needs to directly manage the logic itself.

    class Counter {
      int value = 0;
    
      void increment() {
        value++;
      }
    
      void decrement() {
        value--;
      }
    }
    
    class MySimpleWidget extends StatefulWidget {
      const MySimpleWidget({super.key});
    
      @override
      State<MySimpleWidget> createState() => _MySimpleWidgetState();
    }
    
    class _MySimpleWidgetState extends State<MySimpleWidget> {
      final Counter _counter = Counter(); // Instance of Counter class
    
      @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            Padding(
              padding: const EdgeInsets.only(top: 16.0),
              child: Text('Counter Value: ${_counter.value}'),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  _counter.increment();
                });
              },
              child: const Text('Increment Counter'),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  _counter.decrement();
                });
              },
              child: const Text('Decrement Counter'),
            ),
          ],
        );
      }
    }
    

    Counter Class: This is a simple class that holds the counter’s value and the logic for incrementing and decrementing the counter.
    Separation of Concerns: The _MySimpleWidgetState no longer manages the logic for changing the counter value. Instead, it simply tells the Counter class to perform those operations and calls setState() to rebuild the UI.
    Improved Readability: The _MySimpleWidgetState is now cleaner and only focuses on rendering the UI, making the code more readable and maintainable.

    Hope this helps!

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search