Flutter Navigator.pop(context) returning a black s

2020-02-10 14:14发布

问题:

My Flutter Project structure is like this

Main() //Run App with MaterialApp and Routes
L HomePage() //Default route (/), with BottomNavigation
  L MoviesPage() //Default BottomNavigation Index and shows a list of movies form TMDB 
    L DetailsPage()
  L SeriesPage()
  L SupportPage()

After clicking on any movie it navigates forward to the DetailsPage() but when I call Navigator.pop from DetailsPage() it should go back to the previous screen but it doesn't.

The Navigator.canPop(context) return false But the hardware back button works absolutely fine, so how do I fix it?

main.dart

class BerryMain extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MaterialApp(
      home: Inferno(
        {
          '/': (context, argumets) => HomePage(),
          '/detailspage': (context, arguments) => DetailsPage(arguments),
        },
      ).home(context),
    );
  }
}

homepage

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    // TODO: implement createState
    return _HomePageState();
  }
}

class _HomePageState extends State<HomePage> {
  int _currentIndex = 0;
  final List<Widget> _childnav = [MoviesPage(), SeriesPage(), SupportPage()];

  void onTabPressed(...)

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Scaffold(
      appBar: AppBar(
        title: Text('...'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.search),
            onPressed: () {},
          )
        ],
      ),
      body: _childnav[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        onTap: onTabPressed,
        currentIndex: _currentIndex, //this property defines current active tab
        items: [
          BottomNavigationBarItem(
              icon: Icon(Icons.movie), title: Text('Movies')),
          BottomNavigationBarItem(icon: Icon(Icons.tv), title: Text('Series')),
          BottomNavigationBarItem(icon: Icon(Icons.help), title: Text('Help'))
        ],
      ),
    );
  }
}

MoviesPage

//Inside ListView Builder
Virgil.pushNamed(context, '/detailspage', arguments: args);

DetailsPage

//Inside MaterialApp > Scaffold > SliverAppbar > BackButton
Navigator.pop(context)

I'm using Virgil but I don't think it is the problem.

回答1:

This can happen if your MoviesPage has another MaterialApp widget. Most of the time you want to use Scaffold as a top widget for a new page/screen, but if you accidentally use MaterialApp instead, nothing warns you.

What happens, is that MaterialApp creates a new Navigator, so if you switch from one page with MaterialApp to another, you now have two Navigators in the widget tree.

The call Navigator.of(context) looks for the closest Navigator, so it'll use the one, newly created in your MoviesPage. As the history of your route transitions is stored in a first Navigator, this one can't pop back – it has empty route history.

Hence, the black screen.

Long story short, to fix this, just use Scaffold as a top widget instead of MaterialApp in all nested screens.



回答2:

I resolve it using this code:

Navigator.pushReplacementNamed(context, '/route')


回答3:

I had a very similar problem and my solution was to pass BuildContext context from the created StatelessWidget to a global variable and pop that context from the global variable. So...

var globalContext; // Declare global variable to store context from StatelessWidget

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    globalContext = context; // globalContext receives context from StetelessWidget.

    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        canvasColor: Colors.transparent,
        primarySwatch: Colors.blue,
      ),
      home: SecondScreenPage(title: 'SECOND SCREEN'),
    );
  }
}

class SecondScreenPage extends StatefulWidget {
  SecondScreenPage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _SecondScreenPageState createState() => _SecondScreenPageState();
}

class _SecondScreenPageState extends State<SecondScreenPage>() {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Screen"),
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          onPressed: () => Navigator.pop(globalContext), // POPPING globalContext
        )
      ),
  ...
}

Now I don't get a black screen and I'm at my first page as if I had clicked on Android's back button. =)

I don't know if that is a good practice. If anyone knows a way to do that "more correctly", please feel free to answer.



回答4:

According to this Answer I figured it out a way.

You should pass the MoviesPage context to the DetailsPage. And that context should be called in Navigator.pop(context);

for Example: FIRST SCREEN

class FirstScreen extends StatelessWidget {
      final BuildContext context;

      FirstScreen(this.context);///here u have to call the build Context of FirstScreen

      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
            home: FirstScreenScreen(this.context));
      }
    }

    class FirstScreenScreen extends StatefulWidget {
      final BuildContext context;

      FirstScreenScreen(this.context);/// and here also.

      @override
      _FirstScreenState createState() => _FirstScreenState();
    }

    class _FirstScreenState extends State<FirstScreenScreen> {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
     body: Container(
            child: new RaisedButton(onPressed: (){

               navigateAndDisplaySelection(context);

            },
            child: new Text('Go to SecondPage'),
            ),
          ),
       );
     }
    }

SECOND SCREEN

 class SecondScreen extends StatelessWidget {
      final BuildContext context;

      SecondScreen(this.context);///here u have to call the build Context of FirstScreen

      @override
      Widget build(BuildContext context) {
        return new MaterialApp(
            home: SecondScreenScreen(this.context));
      }
    }

    class SecondScreenScreen extends StatefulWidget {
      final BuildContext context;

      SecondScreenScreen(this.context);/// and here also.

      @override
      _SecondScreenState createState() => _SecondScreenState();
    }

    class _SecondScreenState extends State<SecondScreenScreen> {
      @override
      Widget build(BuildContext context) {
        return new Scaffold(
     body: Container(
            child: new RaisedButton(onPressed: (){
              Navigator.pop(widget.context);///this context is passed from firstScreen
            },
            child: new Text('Go Back'),
            ),
          ),
       );
     }
    }

Pass Data & Return
if you want pass data and return, add a constructor with parameter in SecondScreen and call setState in method Fisrtlike this:

 navigateAndDisplaySelection(
      BuildContext context) async {
    // Navigator.push returns a Future that will complete after we call
    // Navigator.pop on the Second Screen!
    Navigator.push(
      context,
      MaterialPageRoute(
          builder: (context) => new SecondScreen(context)),
    );
    setState(() {
     /// the passed value data will change here, when you change in second screen
    });
  }


回答5:

I had similar issues while poping back views, the way I fixed was instead of Navigator.of(context).pop(); I use Navigator.of(context).maybePop();.

No more black screens after that.



回答6:

Flutter App should have only 1 MaterialApp Why??? It is a core component that give access to all other elements

Technically thinking Material App contains App Title and a Home Screen widget

Here is my implementation

void main() { 
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
  return MaterialApp(
  title: 'First Flutter App',
  home: FirstActivity());
 }
}

class FirstActivity extends StatefulWidget {
   @override
   _FirstActivityState createState() {
   return _FirstActivityState();
   }
}

class _FirstActivityState extends State<FirstActivity> {
   @override
   Widget build(BuildContext context) {
   return Scaffold(
      appBar: AppBar(
      title: Text('First Activity'),
      ),
      body: Center(
      child: RaisedButton(
        child: Text('Move Forward'),
        onPressed: () {
          Navigator.push(
            context,
            MaterialPageRoute(builder: (context) => SecondActivity()),
          );
        }),
        ),
       );
     }
  }

  class SecondActivity extends StatefulWidget {
   @override
   _SecondActivityState createState() {
   return _SecondActivityState();
   }
}

class _SecondActivityState extends State<SecondActivity> {
   @override
   Widget build(BuildContext context) {
   return Scaffold(
      appBar: AppBar(
      title: Text('Second Activity'),
      ),
      body: Center(
      child: RaisedButton(
        child: Text('Move Back'),
        onPressed: () {              
           Navigator.pop(context);
        }),
        ),
       );
     }
  }


回答7:

  1. Import page where you want to go.

  2. Use Class of that Page-Ex.Appointment

Navigator.push(context,new MaterialPageRoute(builder: (context)=> new Appointment() ),);

3. Remove Push name from Navigator


回答8:

Workaround is SystemNavigator.pop(); refer from this link



标签: dart flutter