Como desativar ou sobrescrever o botão “VOLTAR” do Android, no Flutter?

102

Existe uma maneira de desativar o botão Voltar do Android quando estiver em uma página específica?

class WakeUpApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: "Time To Wake Up ?",
      home: new WakeUpHome(),
      routes: <String, WidgetBuilder>{
        '/pageOne': (BuildContext context) => new pageOne(),
        '/pageTwo': (BuildContext context) => new pageTwo(),
      },
    );
  }
}

Na página Um, tenho um botão para ir para a página Dois:

new FloatingActionButton(
  onPressed: () {    
    Navigator.of(context).pushNamed('/pageTwo');
  },
)

Meu problema é que, se pressiono a seta Voltar na parte inferior da tela do Android, volto para a página Um. Eu gostaria que este botão não aparecesse. Idealmente, não gostaria de ter uma saída possível desta tela a menos que o usuário, por exemplo, mantenha o dedo pressionado na tela por 5 segundos. (Estou tentando escrever um aplicativo para crianças pequenas e gostaria que apenas os pais pudessem navegar para fora da tela específica).

Jackpap
fonte

Respostas:

188

A resposta é WillPopScope. Isso impedirá que a página seja exibida pelo sistema. Você ainda poderá usarNavigator.of(context).pop()

@override
Widget build(BuildContext context) {
  return new WillPopScope(
    onWillPop: () async => false,
    child: new Scaffold(
      appBar: new AppBar(
        title: new Text("data"),
        leading: new IconButton(
          icon: new Icon(Icons.ac_unit),
          onPressed: () => Navigator.of(context).pop(),
        ),
      ),
    ),
  );
}
Rémi Rousselet
fonte
1
Para quem deseja interceptar o pop de um Form, é mais conveniente usar a onWillPoppropriedade do Form . Ele tem acesso ao estado do formulário e pode primeiro verificar se há algum estado que o usuário não queira perder.
Joe Lapp
Olá @ RémiRousselet, Você pode me ajudar na pilha de trás a gerenciar como se eu tivesse uma pilha de telas A, B, C, D e desejo navegar de D-> B OU D-> A, então como irei gerenciar isto. Você pode me orientar sobre isso
Ravindra Kushwaha
Eu sei que esta resposta pode ser antiga. Mas é uma GEM! Pense em expandir a explicação. Funcionou excelente. Obrigado.
Val
1
Se alguém quiser fazer algo antes do pop (), pode usar esteonWillPop: () async { onBack(); // "DO YOUR FUNCTION IS HERE WHEN POP" return false; }
Huy Tower
21

Como Rémi Rousselet apontou, WillPopScopegeralmente é o caminho a percorrer. No entanto, se você estiver desenvolvendo um widget com estado que deve reagir diretamente ao botão Voltar, você pode usar isto:

https://pub.dartlang.org/packages/back_button_interceptor

MarcG
fonte
Com todo o respeito, você não acha que isso é mais adequado como um comentário?
CopsOnRoad
21
@CopsOnRoad Não, pois não é um comentário para outra resposta, mas sim uma forma totalmente diferente de resolver o mesmo problema.
MarcG
É uma boa alternativa e poderia ter sido ignorada se fizesse parte de um comentário.
abhijat_saxena
16

Embora a resposta de Remi esteja certa, geralmente você não deseja simplesmente bloquear o botão Voltar, mas deseja que um usuário confirme a saída.

Você pode fazer isso de maneira semelhante, obtendo uma resposta na caixa de diálogo de confirmação, porque onWillPopé um futuro.

@override
Widget build(BuildContext context) {
  return WillPopScope(
    child: Scaffold(...),
    onWillPop: () => showDialog<bool>(
      context: context,
      builder: (c) => AlertDialog(
        title: Text('Warning'),
        content: Text('Do you really want to exit'),
        actions: [
          FlatButton(
            child: Text('Yes'),
            onPressed: () => Navigator.pop(c, true),
          ),
          FlatButton(
            child: Text('No'),
            onPressed: () => Navigator.pop(c, false),
          ),
        ],
      ),
    ),
  );
}
Airon Tark
fonte
Por que essa resposta recebe um downvote? Alguém implementou isso e teve uma experiência ruim?
SamiAzar
Quero ir para a tela anterior, em vez de ir para a primeira tela depois de pressionar o botão Voltar do dispositivo. Eu não preciso de uma caixa de diálogo como esta. O que devo fazer
LakshmiYogeshwaran
4

Estou postando isso aqui, caso alguém aí encontre e deseje encontrar um exemplo simples https://gist.github.com/b-cancel/0ca372017a25f0c120b14dfca3591aa5

import 'package:flutter/material.dart';

import 'dart:async';

void main() => runApp(new BackButtonOverrideDemoWidget());

class BackButtonOverrideDemoWidget extends StatefulWidget{
  @override
  _BackButtonOverrideDemoWidgetState createState() => new _BackButtonOverrideDemoWidgetState();
}

class _BackButtonOverrideDemoWidgetState extends State<BackButtonOverrideDemoWidget> with WidgetsBindingObserver{

  //-------------------------Test Variable

  bool isBackButtonActivated = false;

  //-------------------------Required For WidgetsBindingObserver

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  //-------------------------Function That Triggers when you hit the back key

  @override
  didPopRoute(){
    bool override;
    if(isBackButtonActivated)
      override = false;
    else
      override = true;
    return new Future<bool>.value(override);
  }

  //-------------------------Build Method

  @override
  Widget build(BuildContext context) {
    return new Directionality(
      textDirection: TextDirection.ltr,
      child: new Container(
          color: (isBackButtonActivated) ? Colors.green : Colors.red,
          child: new Center(
              child: new FlatButton(
                color: Colors.white,
                onPressed: () {
                  isBackButtonActivated = !isBackButtonActivated;
                  setState(() {});
                },
                child: (isBackButtonActivated) ?
                new Text("DeActive the Back Button") : new Text("Activate the Back Button"),
              )
          )
      ),
    );
  }
}
Bryan Cancel
fonte
Eu literalmente usei ontem. Lembre-se de que é apenas para Android, a Apple não tem botão Voltar. Embora você possa me dizer o que está errado para que eu possa consertá-lo, ou talvez ele só funcione no meu emulador particular. Não atualizei minha vibração em algumas semanas, então talvez esse seja o problema. Deixe-me saber
Bryan Cancelar
@BryanCancel Sua resposta só funciona se você ainda não tiver nenhuma rota enviada. Veja o método WidgetsBinding.handlePopRoute (). Ele notifica os observadores na ordem de registro e para assim que recebe a confirmação. Quando há rotas empurradas, o navegador retorna verdadeiro primeiro e, em seguida, seu observador nunca é realmente chamado. Em outras palavras, seu código só funciona para evitar que o aplicativo desligue quando o usuário clicar no botão Voltar, quando não houver mais rotas restantes.
MarcG
3

Você pode usar Future.value(bool)para controlar o botão Voltar.

bool _allow = true;

@override
Widget build(BuildContext context) {
  return WillPopScope(
    child: Scaffold(appBar: AppBar(title: Text("Back"))),
    onWillPop: () {
      return Future.value(_allow); // if true allow back else block it
    },
  );
}
CopsOnRoad
fonte
Para sua informação, isso é equivalente a onWillPop: () async => _allow.
Lynn
@Lynn sim, eu sei disso. Eu só queria compartilhar a Future.value()ligação.
CopsOnRoad
3

Eu usei o mixin e o widget WillPopScope simplesmente não consegui fazer o trabalho para mim. Esta é a melhor abordagem que encontrei, muito melhor do que WillPopScope na minha opinião.
final bool canPop = ModalRoute.of(context)?.canPop ?? false;
Usei assim dentro do appbar:

leading: ModalRoute.of(context)?.canPop ?? false
    ? IconButton(
        onPressed: () {
          Navigator.pop(context);
        },
        icon: (Platform.isAndroid)
            ? const Icon(Icons.arrow_back)
            : const Icon(Icons.arrow_back_ios),
      )
    : Container(),
Djordje Komazec
fonte
2

A resposta talvez você saiba que é usar WillPopScope , mas infelizmente no IOS você não pode deslizar para voltar à página anterior, então vamos personalizar seu MaterialPageRoute:

class CustomMaterialPageRoute<T> extends MaterialPageRoute<T> {
  @protected
  bool get hasScopedWillPopCallback {
    return false;
  }
  CustomMaterialPageRoute({
    @required WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  }) : super( 
          builder: builder,
          settings: settings,
          maintainState: maintainState,
          fullscreenDialog: fullscreenDialog,
        );
}

Agora você pode usar o WillPopScope e deslizar para trás para funcionar no IOS. A resposta com os detalhes está aqui: https://github.com/flutter/flutter/issues/14203#issuecomment-540663717

Nhật Trần
fonte
1

Basta adicionar um contêiner no início do Appbar que esconde o botão Voltar.

AppBar(
        title: new Text('Page Title'),
        leading: new Container(),
      ),
Niroop Nife
fonte