flutter_gadgets 0.1.0+1 copy "flutter_gadgets: ^0.1.0+1" to clipboard
flutter_gadgets: ^0.1.0+1 copied to clipboard

discontinued

Flutter Gadgets, a library for state (model) management and service location in Flutter.

example/README.md

Flutter Gadgets Examples #

A collection of Flutter Gadget examples.

Each example shows some of the key elements of the library. For additional information about the gadgets provided by the library, refer to the guide contained in the README.

Rock Paper Scissor 🖐️ #

RockPaperScissor example

The Flutter Gadgets edition of the iconic Rock Paper Scissor game.

In this example, the business logic is represented by two objects:

  • Turn
  • Match
class Turn {
  static final random = Random();

  HandSign _playerChoice;
  HandSign _cpuChoice;
  TurnState state;

  TurnState play(HandSign playerChoice) {
    _playerChoice = playerChoice;
    _cpuChoice = _generateCpuChoice();
    state = _currentTurnState();
    return state;
  }

  HandSign _generateCpuChoice() {
    int index = random.nextInt(HandSign.values.length);
    return HandSign.values[index];
  }

  TurnState _currentTurnState() {
    if (_playerChoice == _cpuChoice) return TurnState.tie;
    if (_playerWon())
      return TurnState.playerWon;
    else
      return TurnState.cpuWon;
  }

  bool _playerWon() {
    bool firstCase =
        _playerChoice == HandSign.paper && _cpuChoice == HandSign.rock;
    bool secondCase =
        _playerChoice == HandSign.rock && _cpuChoice == HandSign.scissor;
    bool thirdCase =
        _playerChoice == HandSign.scissor && _cpuChoice == HandSign.paper;
    return firstCase || secondCase || thirdCase;
  }

  HandSign get playerChoice => _playerChoice;

  HandSign get cpuChoice => _cpuChoice;
}
class Match {
  int cpuWins = 0;
  int playerWins = 0;
  int ties = 0;
  Turn lastTurn;

  void nextTurn(HandSign playerChoice) {
    lastTurn = Turn();
    lastTurn.play(playerChoice);
    _updateWins();
  }

  void _updateWins() {
    if (lastTurnState == TurnState.playerWon) {
      playerWins++;
    } else if (lastTurnState == TurnState.cpuWon) {
      cpuWins++;
    } else {
      ties++;
    }
  }

  MatchState get matchState {
    if (cpuWins == TURNS_TO_WIN) return MatchState.cpuWon;
    if (playerWins == TURNS_TO_WIN) return MatchState.playerWon;
    return MatchState.playing;
  }

  String get matchStateDescription {
    if (cpuWins == TURNS_TO_WIN) return 'Cpu won!';
    if (playerWins == TURNS_TO_WIN) return 'Player won!';
    return 'Playing...';
  }

  TurnState get lastTurnState => lastTurn.state;

  String get lastTurnStateDescription {
    if (lastTurn == null) return 'Your turn.';
    if (lastTurnState == TurnState.playerWon) return 'You won this turn!';
    if (lastTurnState == TurnState.cpuWon) return 'Cpu won this turn!';
    return 'Tie!';
  }
}

When the application is created, a new Match instance is added to the model by providing a <String, Object> map to the bean property of AppGadget:

void main() => runApp(
  AppGadget(
    beans: {
      toBeanKey(Match): Match(),
    },
    child: App(),
  ),
);

Since the game view requires a Match instance to be built, it is wrapped in a SubscriberGadget that grants access to the instance and rebuilds the screen whenever the state of the match changes.

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: _appBar,
      body: _body(context),
    );
  }

  AppBar get _appBar {
    return AppBar(
      title: Text('Rock Paper Scissor'),
      centerTitle: false,
      actions: [
        IconButton(
          icon: Icon(Icons.refresh),
          onPressed: onRefreshPressed,
        )
      ],
    );
  }

  Widget _body(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: SubscriberGadget<Match>(
        builder: (_, Match match) => Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            const Text(
              'Play a match!',
              style: TextStyle(
                fontWeight: FontWeight.w700,
              ),
            ),
            Text(match.matchStateDescription),
            Text(match.lastTurnStateDescription),
            const SizedBox(height: 16),
            RowText('Player wins:', match.playerWins.toString()),
            RowText('Ties:', match.ties.toString()),
            RowText('Cpu wins:', match.cpuWins.toString()),
            const SizedBox(height: 16),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                TextButton(
                  'ROCK',
                  () => onButtonPressed(HandSign.rock),
                  match.matchState == MatchState.playing,
                ),
                TextButton(
                  'PAPER',
                  () => onButtonPressed(HandSign.paper),
                  match.matchState == MatchState.playing,
                ),
                TextButton(
                  'SCISSOR',
                  () => onButtonPressed(HandSign.scissor),
                  match.matchState == MatchState.playing,
                ),
              ],
            ),
            const SizedBox(height: 16),
            HandSignRow()
          ],
        ),
      ),
    );
  }
}

Both control actions use the ServiceContainer to retrieve an instance of the ObservableModel and to get access to the Match object. Then, the state is updated and the view is rebuilt using the putBean and notifyFor methods respectively.

void onRefreshPressed() {
  final model = ServiceContainer.instance.observableModel;
  model.putBean<Match>(Match());
}
void onButtonPressed(HandSign playerChoice) {
  final model = ServiceContainer.instance.observableModel;
  final match = model.getBean<Match>();
  match.nextTurn(playerChoice);
  model.notifyFor(Match);
}

Alien Counter 👽 #

AlienCounter example

A modified version of the classical flutter Counter.

In this example, the business logic is represented by an AlienCounter object. It is defined as follows:

class AlienCounter {
  final livingThings = 1000;
  int aliens = 0;
  int humans = 0;

  String get alienWarning => '$aliens aliens invaded the earth! 😱';

  String get humanWarning => '$humans humans have been kidnapped! 👽';

  String get livingCreature =>
      'There are ${(livingThings - humans) + aliens} $_what on earth 🌍';

  String get _what => aliens == 0 ? 'humans' : 'living... things';

  void addAlien() => aliens += 1;

  void addHuman() => humans += 1;
}

The three text widgets are wrapped in three different SubscriberGadget, each observing a different aspect of the model. Let's consider the first one as an example:

SubscriberGadget<AlienCounter>(
    properties: [P_ALIENS],
    builder: (context, counter) {
      print('Building for $P_ALIENS');
      return Text(counter.alienWarning);
  },
)

It is observing an AlienCounter object contained in the ObservableModel: in particular, it rebuilds only when the observable model notifies changes for the P_ALIENS property of the observed instance, where P_ALIENS is defined as const String P_ALIENS = 'P_ALIENS';

The onPressed callback of each button has the task of modifying the state of the AlienCounter and triggering the rebuild of the view. The callback of the first button, for example, is defined as follows:

void onAddAlien() {
  final model = ServiceContainer.instance.get<ObservableModel>();
  final alienCounter = model.getBean<AlienCounter>();
  alienCounter.addAlien();
  model.notifyFor(AlienCounter, properties: [P_ALIENS]);
}

When the button is pressed, the instance of the ObservableModel is retrieved from the ServiceContainer: at this point, a reference to the counter can be obtained from the model and its state can be changed. The notifyFor method triggers the rebuild of the view, that will recreate only those widgets which are observing the AlienCounter instance and its P_ALIENS property.

Number Guess ❓ #

RockPaperScissor example

Guess a random number between 1 and 100.

In this example, both views have a local internal state.

Let's look at the screen from the image: there is a TextField widget with an associated TextEditingController. To be able to dispose the TextEditingController and manipulate the internal state of the TextField (the errorText property for example), a ViewModelGadget encapsulates the view.

By defining a GameScreenVM and by using a ViewModelGadget, an instance of the GameScreenVM is automatically added to the ObservableModel and removed when the view is destroyed. In this way, widgets that require the GameScreenVM instance can use the SubscriberGadget to access it and start listening for eventual changes.

class GameScreenVM {
  TextEditingController controller;
  String errorMessage;
  bool canPlay = true;
}
return ViewModelGadget<GameScreenVM>(
      GameScreenVM(),
      initStateCallback: (final viewModel) {
        viewModel.controller = TextEditingController();
      },
      disposeCallback: (final viewModel) {
        viewModel.controller.dispose();
      },
      child: _child(context),
    );
  }

The internal state of the view model instance can be updated as usual. Let's look at the onResetPressed callback:

void onResetPressed() {
  final model = ServiceContainer.instance.observableModel;
  final game = model.getBean<Game>();
  game.newGame();
  final viewModel = model.getBean<GameScreenVM>();
  viewModel.controller.clear();
  viewModel.errorMessage = null;
  viewModel.canPlay = true;
  model.notifyFor(GameScreenVM);
  model.notifyFor(Game);
}
6
likes
40
pub points
0%
popularity

Publisher

verified publishersvelto.tech

Flutter Gadgets, a library for state (model) management and service location in Flutter.

Repository

License

BSD-3-Clause (LICENSE)

Dependencies

flutter

More

Packages that depend on flutter_gadgets