Flutter. Show dialogs, toasts, etc. in your ViewModel/Controller without BuildContext

Yuri Novicow
Easy Flutter
·
·

One of the best ideas of GetX is showing dialogs, toasts, snackbars, and sheets without BuildContext.

It is extremely convenient since we can show a dialog or a toast from the Controller instead of View.

If you are a member, please continue, otherwise, read the full story here.

Consider an example from a recent article.
We have a checkers game and we check the win condition after every move:

game_controller.dart

void movePiece() {
...
checkForVictory();
}

void checkForVictory() {
...
if (isVictory){
DialogManager().showWinDialog();
} else {
DialogManager().showDefeatDialog();
}
}

dialog_manager.dart

void showWinDialog(){
Get.dialog(
//no context required
)
}

The dialog is not shown without context, actually. It would be impossible I guess. But, GetX holds the BuildContext object of the last screen in the navigator stack and uses this context internally to show dialogs (toasts and so on).

This context is always available via Get.context property and can be used, for example, with third-party dialog packages.

Let’s imagine that for some reason we do not use GetX.

(BTW, what reason it can be? Maybe indoctrination.)

Then, what options do we have?

First, we can pass BuildContext from View to Controller.

game_view.dart

onTap: () {         
controller.movePiece(context);
},

Then pass through all Controller’s methods:

void movePiece(context) {
...
checkForVictory(context);
}

void checkForVictory(context) {
...
if (isVictory){
DialogManager().showWinDialog(context);
} else {
DialogManager().showDefeatDialog(context);
}
}

Verbose, messy, buggy, and so on. I have no good adjectives for this solution. Also controller should not know about the context, otherwise, it is a violation of the Separation of Concerns.

The second option, we can return value from the controller:

move_result.dart

class MoveResult {
bool isGameFinished = false;
bool isWhiteWon = false;

MoveResult(this.isGameFinished, this.isWhiteWon);
}

game_view.dart

onTap: () {         
MoveResult result = controller.movePiece();
if (MoveResult.isGameFinished ){
if (MoveResult.isWhiteWon){
DialogManager().showWinDialog(context);
} else {
DialogManager().showDefeatDialog(context);
}
}
},

game_controller.dart

MoveResult movePiece() {
...
return checkForVictory();
}

MoveResult checkForVictory() {
...
if (isVictory){
return MoveResult(true, true);
} else {
return MoveResult(true, false);
}
}

This solution is better and this is what people who don’t use GetX usually do, I guess.
However, this solution has its drawbacks. We added a whole new class and some not-smart-looking logic to the view (violation of Single Responsibility and Separation of Concerns).

I think, now we clearly see what problem GetX solves by allowing to show dialogs without context. (Viva el GetX!).

At this point, I could happily finish and go to code my next app.

But, because I am a kind person, I want to propose a solution that people who don’t use GetX (yet) can use to show dialogs more conveniently.

Consider this DialogManager implementation:

class DialogManager {
DialogManager._();
static final _instance = DialogManager._();
factory DialogManager() {
return _instance;
}

BuildContext? _context;
set context(val) => _context = val;


void showWinDialog() {
QuickAlert.show(
context: _context!,
type: QuickAlertType.success,
title: 'Victory!',
text: 'Congratulations. You won!',
btnText: 'Ok',
);
}

void showDefeatDialog() {
QuickAlert.show(
context: _context!,
type: QuickAlertType.failure,
title: 'Defeat!',
text: 'Do you want to try again?',
btnText: 'Ok',
);
}
}

(Showing dialogs is a pseudo-code using the QuickAlert package. Just for simplicity.)

The point is that DialogManager is a singleton and it has a BuildContext setter.

Then in every screen widget where we want to show dialogs (toasts, etc.), we call the BuildContext setter from the build method:

game_view.dart

class GameView extends StatelessWidget{
...


Widget build(BuildContext context){
DialogManager().context = context;
return Scaffold (
...
);
}
}

Voila. Now the DialogManager has the current context and we can show dialogs from the Controller “without context” the same way we were doing it with GetX:

void movePiece() {
...
checkForVictory();
}

void checkForVictory() {
...
if (isVictory){
DialogManager().showWinDialog();
} else {
DialogManager().showDefeatDialog();
}
}

That’s all. Thank you for reading and happy coding!

If you like the solution, please clap. ๐Ÿ‘๐Ÿ‘๐Ÿ‘
Otherwise, leave a comment.

Comments