How to open new MaterialPageRoute as a child in widget tree in Flutter

3,383

Solution 1

I solved my issue using nested Navigator.

Here is the example:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: Home(),
  ));
}

var homeNavigatorKey = GlobalKey<NavigatorState>();

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Navigator(
        key: homeNavigatorKey,
        onGenerateRoute: (settings) {
          /*dirty code for illustration purposes only*/
          if (settings.name == '/child') {
            return MaterialPageRoute(builder: (context) => Child());
          } else {
            return MaterialPageRoute(
                builder: (context) => Column(
                      children: <Widget>[
                        Center(
                          child: Text("This is home"),
                        ),
                        RaisedButton(
                          child: Text("Open child view"),
                          onPressed: () {
                            homeNavigatorKey.currentState.pushNamed('/child');
                          },
                        )
                      ],
                    ));
          }
        },
      ),
    );
  }
}

class Child extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      child: RaisedButton(
        onPressed: () => Navigator.of(context).pop(),
        child: Text("Back to home"),
      ),
    ));
  }
}

The tree looks like this

enter image description here

and allows for passing data from Home to every child.

Feel free to comment or post answers if you have any simpler solution.

Solution 2

Navigator.of(context).push() will replace your Home screen with a new screen (while keeping the Home screen in memory so you can go back to it). They are on the same level in the widget tree, the only way to nest them is to use nested Navigators I think.

But, given what you want to achieve: why not initialize the ScopedModel one level up and provide at the root of your widget tree? That way you can access your model both in the Home screen as well as the Child screen.

Below is how you could do that, I added a simple ScopedModel and am able to access it's textproperty inside the Child screen.

import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'package:tryout/model.dart';

void main() {
  runApp(
    ScopedModel<TestModel>(
      model: TestModel(),
      child: MaterialApp(
        home: Home(),
      ),
    )
  );
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Center(
            child: Text("This is home"),
          ),
          RaisedButton(
            child: Text("Open child view"),
            onPressed: () {
              Navigator.of(context)
                  .push(MaterialPageRoute(builder: (context) => Child()));
            },
          )
        ],
      ),
    );
  }
}

class Child extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Container(
      child: Text("${ScopedModel.of<TestModel>(context).text}"),
    ));
  }
}

And here's the TestModel class:

import 'package:scoped_model/scoped_model.dart';

class TestModel extends Model {
  String _text = "Test to see if this works!";
  String get text => _text;
}
Share:
3,383
mbartn
Author by

mbartn

Updated on December 19, 2022

Comments

  • mbartn
    mbartn over 1 year

    In the example below when I push new MaterialPageRoute it is created on the same level as Home widget in the Flutter widget tree. I would like to have it as a child of widget Home, so Home would be a parent of Child widget.

    Here is a full code:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(MaterialApp(
        home: Home(),
      ));
    }
    
    class Home extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(
            children: <Widget>[
              Center(
                child: Text("This is home"),
              ),
              RaisedButton(
                child: Text("Open child view"),
                onPressed: () {
                  Navigator.of(context)
                      .push(MaterialPageRoute(builder: (context) => Child()));
                },
              )
            ],
          ),
        );
      }
    }
    
    class Child extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            body: Container(
          child: Text("Child view"),
        ));
      }
    }
    

    Here is how the widget tree looks like

    enter image description here

    I want to achieve that because I would like to initialize the ScopedModel in Home widget and have it available in every new MaterialPageRoute I create.

  • mbartn
    mbartn about 4 years
    Thanks for your answer. I know about that possibility and that's how I have it currently implemented but I have a complex application with multiple different models and I want some of them to recreate when specific widget is recreated (Home in this example represents this widget). I was wondering if there is any simpler approach than implementing nested Navigators.
  • JJuice
    JJuice about 4 years
    Aaah ok. Maybe you could do it by first disposing the already initialized model, before doing your Navigators.push()?
  • mbartn
    mbartn about 4 years
    At this point I have a dispose() method in these models but I don't want to clean models by hand because every time a new programmer adds a new variable/functionality to the model he has to remember to dispose it in dispose() method and I don't like it. You think there is any possibility to assign a brand new object instance for already initialized model? I mean something like ScopedModel.of<TestModel>(context) = TestModel()?
  • JJuice
    JJuice about 4 years
    I am not aware of the possibility to recreate instances, all across your widgettree. Passing down a new instance via the constructor of the Child widget, instead of letting InheritedWidget work for you is something you shouldn’t do I think, because it will create multiple instances and states of your model, which is exactly the opposite of what the package is all about.
  • JJuice
    JJuice about 4 years
    But, why do you need a new instance in the first place? What's the problem that recreating the model should solve?
  • mbartn
    mbartn about 4 years
    For example: Some user data is stored in ExampleModel (let's say his personal notes). When user logs out I have to remove all his session data stored in ExampleModel and pop() Home widget. When the next user logs in he should have clean or brand new ExampleModel when entering Home widget.
  • JJuice
    JJuice about 4 years
    I’ll think about it in the weekend. Found a Reddit topic which is about Provider (but Provider is like scoped model 2.0). I guess you need to make sure to rebuilt you’re widget tree from the point where the scoped model is injected, and up to there your Navigation stack needs to be popped.