pushReplacementNamed doesn't replace it just pushes on top if coming from an AlertDialog

Viewed 48

Here's the code:

3 FILES home, main, and screen1

MAIN:

import 'package:flutter/material.dart';
import 'home.dart';
import 'screen1.dart';

void main() {
  runApp(MaterialApp(
    home: Home(),
    routes:
    {
      '/home' : (context) => Home(),
      '/screen1': (context) => Screen1()
    },
  ));
}

HOME:

import 'package:flutter/material.dart';

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          onPressed: (){Navigator.pushReplacementNamed(context, '/screen1');},
          child: Text('screen 1'),
        ),
      ),
    );
  }
}

screen1:

import 'package:flutter/material.dart';

Widget homeDialog(BuildContext context){
  return AlertDialog(
    title: const Text('Do you want to exit'),
    actions: [
      ElevatedButton(
        child: const Text('No'),
        onPressed: (){
          Navigator.pop(context);
        },
      ),
      ElevatedButton(
        child: const Text('Yes'),
        onPressed: (){
          Navigator.pushReplacementNamed(context, '/home');
        },
      ),
    ],
  );
}

class Screen1 extends StatefulWidget {
const Screen1({Key? key}) : super(key: key);

@override
State<Screen1> createState() => _Screen1State();
}

class _Screen1State extends State<Screen1> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading:  false,
       title: Text('screen 1'),
      leading: GestureDetector(
      onTap: (){showDialog(context: context, builder: homeDialog);},
      child: Icon(Icons.home)
      ),
    ),
   );
}
}
1 Answers

This happens because showDialog(AlertDialog(...)) is itself going to be pushed as a new route. So, when doing Navigator.pushReplacementNamed inside the alert it is going to replace the alert itself, not the previous screen. That's why Home and Screen1 got stacked on each other.

To solve this problem an AlertDialog should only return by a Navigator.pop(context, result) passing the result as an argument of it. The sources are going to be the following:

AlertDialog

Widget homeDialog(BuildContext context) {
  return AlertDialog(
    title: const Text('Do you want to exit'),
    actions: [
      ElevatedButton(
        child: const Text('No'),
        onPressed: () => Navigator.pop(context, false),
      ),
      ElevatedButton(
        child: const Text('Yes'),
        onPressed: () => Navigator.pop(context, true),
      ),
    ],
  );
}

Screen1

class Screen1 extends StatefulWidget {
  const Screen1({Key? key}) : super(key: key);

  @override
  State<Screen1> createState() => _Screen1State();
}

class _Screen1State extends State<Screen1> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        automaticallyImplyLeading: false,
        title: Text('screen 1'),
        leading: GestureDetector(
            onTap: () async {
              bool? yes =
                  await showDialog<bool>(context: context, builder: homeDialog);
              // `mounted` checks if this screen hasn't been disposed already.
              if (mounted) {
                if (yes == true) {
                  Navigator.pushReplacementNamed(context, '/home');
                } else {
                  Navigator.pop(context);
                }
              }
            },
            child: Icon(Icons.home)),
      ),
    );
  }
}
Related