RegularGuy
RegularGuy

Reputation: 3676

Bring screen to the top of navigator

I have been searching for a while and i could not find any option to do this. Is this even possible?

In the following scenario

Page1 > Page2 > Page3 > Page4

From Page4 , can i navigate to Page2 without creating a new Page2?

Desired result:

Page1 >??empty?? > Page3 > Page4 > Page2

Normal result:

Page1 > Page2 > Page3 > Page4 > different Page2

Is this even possible with flutter?

Upvotes: 1

Views: 656

Answers (2)

RegularGuy
RegularGuy

Reputation: 3676

Ok after a while i found an answer.

The ideal answer should have some prerequisites

  • It should retain it's state when pushed to the top
  • It should be able to be pushed from anywhere in the app
  • It should be the same screen [not a rebuilded one]
  • It should be able to have a custom transition
  • It should not unmount by any means [state is lost]

And the most important one

  • It should be able to go to the top of any navigator, but it shouldn't be able to stay in the navigator tree.

The solution

A Backdrop widget in the root Scaffold of the app

Looking for this i have found this medium article of a backdrop.

For my personal use i removed the swipe capability, and added a back button handler (so it acts as a screen in a navigator).

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';

const _kFlingVelocity = 2.0;

class Backdrop extends StatefulWidget {
  final Widget frontLayer;
  final Widget backLayer;
  final ValueNotifier<bool> panelVisible;

  Backdrop(
      {@required this.frontLayer, @required this.backLayer, this.panelVisible})
      : assert(frontLayer != null),
        assert(backLayer != null);

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

class _BackdropState extends State<Backdrop>
    with SingleTickerProviderStateMixin {
  final _backdropKey = GlobalKey(debugLabel: 'Backdrop');
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: Duration(milliseconds: 300),
      value: (widget.panelVisible?.value ?? true) ? 1.0 : 0.0,
      vsync: this,
    );

    widget.panelVisible?.addListener(_subscribeToValueNotifier);
    if (widget.panelVisible != null) {
      _controller.addStatusListener((status) {
        if (status == AnimationStatus.completed)
          widget.panelVisible.value = true;
        else if (status == AnimationStatus.dismissed)
          widget.panelVisible.value = false;
      });
    }
  }

  void _subscribeToValueNotifier() {
    if (widget.panelVisible.value != _backdropPanelVisible)
      _toggleBackdropPanelVisibility();
  }

  @override
  void didUpdateWidget(Backdrop oldWidget) {
    super.didUpdateWidget(oldWidget);
    oldWidget.panelVisible?.removeListener(_subscribeToValueNotifier);
    widget.panelVisible?.addListener(_subscribeToValueNotifier);
  }

  @override
  void dispose() {
    _controller.dispose();
    widget.panelVisible?.dispose();
    super.dispose();
  }

  bool get _backdropPanelVisible =>
      _controller.status == AnimationStatus.completed ||
      _controller.status == AnimationStatus.forward;

  void _toggleBackdropPanelVisibility() => _controller.fling(
      velocity: _backdropPanelVisible ? -_kFlingVelocity : _kFlingVelocity);

  Future<bool> _onWillPop() async {
    if (widget.panelVisible.value) {
      widget.panelVisible.value = false;
      return false;
    }

    return true;
  }

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(builder: (context, constraints) {
      final panelDetailsPosition = Tween<Offset>(
        begin: Offset(0.0, 1.0),
        end: Offset(0.0, 0.0),
      ).animate(_controller.view);

      return WillPopScope(
          onWillPop: _onWillPop,
          child: Container(
            key: _backdropKey,
            child: Stack(
              children: <Widget>[
                widget.backLayer,
                SlideTransition(
                    position: panelDetailsPosition, child: widget.frontLayer),
              ],
            ),
          ));
    });
  }
}

I have the final frontPanelVisible = ValueNotifier<bool>(false); in a global store , so i can call it from anywhere in the app and "push" this magic screen that teleports.

And in the main widget of my app i have something like this

class Panels extends StatelessWidget {
  final frontPanelVisible = ValueNotifier<bool>(false);

  @override
  Widget build(BuildContext context) {
    return Backdrop(
      frontLayer: MainAppScreen(),
      backLayer: MagicalScreen(),
      panelVisible: frontPanelVisible,
    );
  }
}

It's quite magical to jump in the hierarchy, also, it saves a ton of resources [since instead of having 15 screens of the same content i have only 1], and maybe with a little tinkering i can have a nested navigator there . The possibilities are endless.

Upvotes: 1

Nouh Belahcen
Nouh Belahcen

Reputation: 854

Yes you can back go back from Page4 to Page2

Navigator.popUntil(context, ModalRoute.withName("/screen2"));

Upvotes: 2

Related Questions