Buộc điều hướng Flutter tải lại trạng thái khi bật lên


98

Tôi có một cái StatefulWidgettrong Flutter with button, điều hướng tôi đến cái khác StatefulWidgetbằng cách sử dụng Navigator.push(). Trên tiện ích thứ hai, tôi đang thay đổi trạng thái toàn cầu (một số tùy chọn của người dùng). Khi tôi quay lại từ tiện ích thứ hai sang tiện ích thứ nhất, việc sử dụng Navigator.pop()tiện ích đầu tiên ở trạng thái cũ, nhưng tôi muốn buộc nó tải lại. Bất kỳ ý tưởng làm thế nào để làm điều này? Tôi có một ý tưởng nhưng nó trông xấu xí:

  1. bật lên để xóa tiện ích con thứ hai (tiện ích hiện tại)
  2. bật lại để xóa tiện ích con đầu tiên (tiện ích trước đó)
  3. push widget đầu tiên (nó sẽ buộc phải vẽ lại)

1
Không có câu trả lời, chỉ là một nhận xét chung: trong trường hợp của tôi, điều đưa tôi đến đây để tìm kiếm điều này sẽ được giải quyết chỉ cần có một phương pháp đồng bộ hóa cho shared_preferences nơi tôi đảm bảo lấy lại bản cập nhật mà tôi đã viết chỉ một lúc trước trên một trang khác . : \ Ngay cả khi sử dụng .then (...) không phải lúc nào tôi cũng nhận được dữ liệu cập nhật đang chờ ghi.
ChrisH

Vừa trả về một giá trị từ trang mới trên cửa sổ bật lên, đã giải quyết được vấn đề của tôi. Tham khảo flay.dev/docs/cookbook/navigation/returning-data
Joe M

Câu trả lời:


79

Có một số điều bạn có thể làm ở đây. Câu trả lời của @ Mahi trong khi đúng có thể ngắn gọn hơn một chút và thực sự sử dụng push chứ không phải showDialog như OP đã hỏi. Đây là một ví dụ sử dụng Navigator.push:

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.green,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
            onPressed: () => Navigator.pop(context),
            child: new Text("back"),
          ),
        ],
      ),
    );
  }
}

class FirstPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new FirstPageState();
}

class FirstPageState extends State<FirstPage> {

  Color color = Colors.white;

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: color,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator
                    .push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                )
                    .then((value) {
                  setState(() {
                    color = color == Colors.white ? Colors.grey : Colors.white;
                  });
                });
              }),
        ],
      ),
    );
  }
}

void main() => runApp(
      new MaterialApp(
        builder: (context, child) => new SafeArea(child: child),
        home: new FirstPage(),
      ),
    );

Tuy nhiên, có một cách khác để làm điều này có thể phù hợp với trường hợp sử dụng của bạn. Nếu bạn đang sử dụng globallàm thứ ảnh hưởng đến việc xây dựng trang đầu tiên của mình, bạn có thể sử dụng InheritedWidget để xác định tùy chọn người dùng toàn cầu của mình và mỗi khi chúng được thay đổi, FirstPage của bạn sẽ xây dựng lại. Điều này thậm chí hoạt động trong một tiện ích không trạng thái như được hiển thị bên dưới (nhưng cũng sẽ hoạt động trong một tiện ích trạng thái).

Một ví dụ về inheritWidget in flashing là Chủ đề của ứng dụng, mặc dù họ xác định nó trong một widget thay vì để nó trực tiếp xây dựng như tôi có ở đây.

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

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.green,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
            onPressed: () {
              ColorDefinition.of(context).toggleColor();
              Navigator.pop(context);
            },
            child: new Text("back"),
          ),
        ],
      ),
    );
  }
}

class ColorDefinition extends InheritedWidget {
  ColorDefinition({
    Key key,
    @required Widget child,
  }): super(key: key, child: child);

  Color color = Colors.white;

  static ColorDefinition of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(ColorDefinition);
  }

  void toggleColor() {
    color = color == Colors.white ? Colors.grey : Colors.white;
    print("color set to $color");
  }

  @override
  bool updateShouldNotify(ColorDefinition oldWidget) =>
      color != oldWidget.color;
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var color = ColorDefinition.of(context).color;

    return new Container(
      color: color,
      child: new Column(
        children: <Widget>[
          new RaisedButton(
              child: new Text("next"),
              onPressed: () {
                Navigator.push(
                  context,
                  new MaterialPageRoute(builder: (context) => new SecondPage()),
                );
              }),
        ],
      ),
    );
  }
}

void main() => runApp(
      new MaterialApp(
        builder: (context, child) => new SafeArea(
              child: new ColorDefinition(child: child),
            ),
        home: new FirstPage(),
      ),
    );

Nếu bạn sử dụng tiện ích con kế thừa, bạn không phải lo lắng về việc xem cửa sổ bật lên của trang bạn đã đẩy, điều này sẽ hoạt động cho các trường hợp sử dụng cơ bản nhưng cuối cùng có thể gặp sự cố trong một tình huống phức tạp hơn.


Tuyệt vời, trường hợp đầu tiên làm việc cho tôi một cách hoàn hảo (trường hợp thứ hai là hoàn hảo cho một tình huống phức tạp hơn). Tôi không rõ việc sử dụng OnWillPopUp từ trang thứ hai; và thậm chí không hoạt động.
cdsaenz

Này, nếu tôi muốn cập nhật mục danh sách của mình thì sao. Giả sử trang đầu tiên của tôi chứa các mục danh sách và trang thứ hai chứa chi tiết mục. Tôi đang cập nhật giá trị của mặt hàng và tôi muốn nó được cập nhật lại tại các mặt hàng trong danh sách. Làm thế nào để đạt được điều đó?
Aanal Mehta

@AanalMehta Tôi có thể đưa ra đề xuất nhanh cho bạn - có một cửa hàng phụ trợ (tức là bảng sqflite hoặc danh sách trong bộ nhớ) được sử dụng từ cả hai trang và trong. Thì bạn có thể buộc tiện ích của mình làm mới bằng cách nào đó (cá nhân tôi đã sử dụng bộ đếm tăng dần mà tôi đã thay đổi trong setState trước đây - nó không phải là giải pháp sạch nhất nhưng nó hoạt động) ... Hoặc, bạn có thể chuyển các thay đổi trở lại trang ban đầu trong hàm .then, thực hiện sửa đổi cho danh sách của bạn và sau đó xây dựng lại (nhưng lưu ý rằng việc thực hiện thay đổi đối với danh sách không kích hoạt làm mới, vì vậy một lần nữa hãy sử dụng bộ đếm tăng dần).
rmtmckenzie

@AanalMehta Nhưng nếu điều đó không hữu ích, tôi khuyên bạn nên tìm kiếm một số câu trả lời khác có thể phù hợp hơn (tôi khá chắc chắn rằng tôi đã xem một số về danh sách, v.v.) hoặc đặt một câu hỏi mới.
rmtmckenzie

@rmtmckenzie Trên thực tế, tôi đã thử giải pháp ở trên. Tôi đã áp dụng ges chan trong. Sau đó nhưng nó sẽ không được phản ánh. Tôi đoán bây giờ tôi chỉ có một lựa chọn để sử dụng các nhà cung cấp. Dù sao, cảm ơn bạn rất nhiều vì sự giúp đỡ của bạn.
Aanal Mehta

19

Có 2 điều, chuyển dữ liệu từ

  • Trang đầu tiên đến trang thứ hai

    Sử dụng cái này trong trang đầu tiên

    // sending "Foo" from 1st
    Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
    

    Sử dụng điều này trong trang thứ hai.

    class Page2 extends StatelessWidget {
      final String string;
    
      Page2(this.string); // receiving "Foo" in 2nd
    
      ...
    }
    

  • Trang thứ 2 đến Trang thứ nhất

    Sử dụng cái này ở trang thứ 2

    // sending "Bar" from 2nd
    Navigator.pop(context, "Bar");
    

    Sử dụng điều này trong trang đầu tiên, nó giống như đã được sử dụng trước đó nhưng với một chút sửa đổi.

    // receiving "Bar" in 1st
    String received = await Navigator.push(context, MaterialPageRoute(builder: (_) => Page2("Foo")));
    

Làm cách nào tôi có thể tải lại tuyến đường A khi xuất hiện từ tuyến đường C bằng phương pháp Navigator.popUntil.
Vinoth Vino

1
@VinothVino Chưa có cách trực tiếp nào để làm điều đó, bạn cần thực hiện một số cách giải quyết khác.
CopsOnRoad

10

Bạn có thể sử dụng pushReplacement và chỉ định Tuyến đường mới


Nhưng bạn đang mất ngăn xếp điều hướng. Điều gì sẽ xảy ra nếu bạn bật màn hình bằng nút quay lại Android?
encubos

10

Các Dễ Lừa là sử dụng Navigator.pushReplacement phương pháp

Trang 1

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page2(),
  ),
);

Trang 2

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => Page1(),
  ),
);

Bằng cách này, bạn đang mất ngăn xếp điều hướng. Còn việc bật màn hình bằng nút quay lại thì sao?
encubos

5

Tác phẩm này thực sự rất hay, tôi lấy từ tài liệu này từ trang rung :

Tôi đã xác định phương pháp để kiểm soát điều hướng từ trang đầu tiên.

_navigateAndDisplaySelection(BuildContext context) async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => AddDirectionPage()),
    );

    //below you can get your result and update the view with setState
    //changing the value if you want, i just wanted know if i have to  
    //update, and if is true, reload state

    if (result) {
      setState(() {});
    }
  }

Vì vậy, tôi gọi nó trong một phương thức hành động từ ống mực, nhưng cũng có thể được gọi từ một nút:

onTap: () {
   _navigateAndDisplaySelection(context);
},

Và cuối cùng trong trang thứ hai, để trả lại một cái gì đó (tôi đã trả lại bool, bạn có thể trả lại bất cứ thứ gì bạn muốn):

onTap: () {
  Navigator.pop(context, true);
}

1
Bạn thậm chí có thể chỉ await Navigator....và bỏ qua một giá trị kết quả trong cửa sổ bật lên.
0llie

4

Đặt cái này ở nơi bạn đang đẩy sang màn hình thứ hai (bên trong một chức năng không đồng bộ)

Function f;
f= await Navigator.pushNamed(context, 'ScreenName');
f();

Đặt cái này ở nơi bạn đang bật

Navigator.pop(context, () {
 setState(() {});
});

Các setStateđược gọi bên trong popđóng cửa để cập nhật dữ liệu.


2
Chính xác nơi nào bạn chuyển setStatelàm đối số? Về cơ bản, bạn đang gọi setStatebên trong một đóng cửa. Không chuyển nó như một đối số.
Volkan Güven

Đây là câu trả lời chính xác mà tôi đang tìm kiếm. Về cơ bản tôi muốn một trình xử lý hoàn thành cho Navigator.pop.
David Chopin

2

giải pháp của tôi là thêm một tham số hàm trên SecondPage, sau đó nhận hàm tải lại đang được thực hiện từ FirstPage, sau đó thực thi hàm trước dòng Navigator.pop (context).

Trang đầu tiên

refresh() {
setState(() {
//all the reload processes
});
}

sau đó chuyển sang trang tiếp theo ...

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

Trang thứ hai

final Function refresh;
SecondPage(this.refresh); //constructor

sau đó đến trước dòng bật lên của trình điều hướng,

widget.refresh(); // just refresh() if its statelesswidget
Navigator.pop(context);

Mọi thứ cần tải lại từ trang trước phải được cập nhật sau cửa sổ bật lên.


1

Bạn có thể trả lại a dynamic resultkhi bạn đang bật ngữ cảnh và sau đó gọi setState((){})giá trị khi nào, truenếu không thì chỉ cần để nguyên trạng thái.

Tôi đã dán một số đoạn mã để bạn tham khảo.

handleClear() async {
    try {
      var delete = await deleteLoanWarning(
        context,
        'Clear Notifications?',
        'Are you sure you want to clear notifications. This action cannot be undone',
      );
      if (delete.toString() == 'true') {
        //call setState here to rebuild your state.

      }
    } catch (error) {
      print('error clearing notifications' + error.toString());
             }
  }



Future<bool> deleteLoanWarning(BuildContext context, String title, String msg) async {

  return await showDialog<bool>(
        context: context,
        child: new AlertDialog(
          title: new Text(
            title,
            style: new TextStyle(fontWeight: fontWeight, color: CustomColors.continueButton),
            textAlign: TextAlign.center,
          ),
          content: new Text(
            msg,
            textAlign: TextAlign.justify,
          ),
          actions: <Widget>[
            new Container(
              decoration: boxDecoration(),
              child: new MaterialButton(
                child: new Text('NO',),
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
              ),
            ),
            new Container(
              decoration: boxDecoration(),
              child: new MaterialButton(
                child: new Text('YES', ),
                onPressed: () {
                  Navigator.of(context).pop(true);
                },
              ),
            ),
          ],
        ),
      ) ??
      false;
}

Trân trọng, Mahi


Tôi không chắc mình hiểu cách làm phần thứ hai. Đầu tiên là chỉ đơn giản Navigator.pop(context, true), phải không? Nhưng làm thế nào tôi có thể nhận được truegiá trị này ? Sử dụng BuildContext?
bartektartanus

Không làm việc cho tôi. Tôi đã sử dụng kết quả từ đẩy, gọi setstate và nhậnsetState() called after dispose(): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. ....
bartektartanus

hmm, điều này thật thú vị mà bạn đã sử dụng if(!mounted) return;trước mỗi cuộc gọi setState((){});theo cách này, bạn có thể tránh cập nhật các widget bị loại bỏ hoặc các widget không còn hoạt động.
Mahi

Không có lỗi nhưng cũng có thể nó không hoạt động như tôi dự đoán;)
bartektartanus

Tôi gặp vấn đề tương tự với @bartektartanus. Nếu tôi thêm if! Mount trước setState, trạng thái của tôi sẽ không bao giờ được đặt. Nó có vẻ đơn giản nhưng tôi đã không tìm ra nó sau vài giờ.
Swift

1

Cần thiết để buộc xây dựng lại một trong các vật dụng không trạng thái của tôi. Không muốn sử dụng trạng thái. Đưa ra giải pháp này:

await Navigator.of(context).pushNamed(...);
ModalRoute.of(enclosingWidgetContext);

Lưu ý rằng ngữ cảnh và enclosingWidgetContext có thể là các ngữ cảnh giống nhau hoặc khác nhau. Ví dụ: nếu bạn đẩy từ bên trong StreamBuilder, chúng sẽ khác.

Chúng tôi không làm bất cứ điều gì ở đây với ModalRoute. Chỉ riêng hành động đăng ký cũng đủ để buộc phải xây dựng lại.


1

Nếu bạn đang sử dụng hộp thoại cảnh báo thì bạn có thể sử dụng Tương lai hoàn thành khi hộp thoại bị loại bỏ. Sau khi hoàn thành trong tương lai, bạn có thể buộc widget tải lại trạng thái.

Trang đầu tiên

onPressed: () async {
    await showDialog(
       context: context,
       builder: (BuildContext context) {
            return AlertDialog(
                 ....
            );
       }
    );
    setState(() {});
}

Trong hộp thoại Cảnh báo

Navigator.of(context).pop();

0

Hôm nay tôi gặp phải tình huống tương tự nhưng tôi đã giải quyết được nó theo cách dễ dàng hơn nhiều, tôi chỉ xác định một biến toàn cục được sử dụng trong lớp trạng thái đầu tiên và khi tôi điều hướng đến tiện ích con trạng thái thứ hai, tôi để nó cập nhật giá trị của toàn cục biến tự động buộc tiện ích con đầu tiên cập nhật. Đây là một ví dụ (tôi viết nó vội vàng vì vậy tôi không đặt một giàn giáo hoặc một ứng dụng material, tôi chỉ muốn minh họa quan điểm của mình):

import 'package:flutter/material.dart';
int count = 0 ;

class FirstPage extends StatefulWidget {
FirstPage({Key key}) : super(key: key);

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

class _FirstPageState extends State<FirstPage> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap(){
Navigator.of(context).push(MaterialPageRoute(builder: (context) =>
                  new SecondPage());
},
child: Text('First', style : TextStyle(fontSize: count == 0 ? 20.0 : 12.0)),
),

}


class SecondPage extends StatefulWidget {
SecondPage({Key key}) : super(key: key);

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

class _SecondPageState extends State<SecondPage> {
@override
Widget build(BuildContext context) {
return IconButton(
         icon: new Icon(Icons.add),
         color: Colors.amber,
         iconSize: 15.0,
         onPressed: (){
         count++ ;
         },
       ),
     }

4
Thay đổi biến sẽ không tự động xây dựng lại tiện ích con. Sau khi Navigator.pop()nó sẽ giữ trạng thái trước đó Navigator.push()cho đến khi setStateđược gọi lại, những gì không xảy ra trong mã này. Tui bỏ lỡ điều gì vậy?
Ricardo BRGWeb

0

Mã đơn giản này hoạt động để tôi truy cập thư mục gốc và tải lại trạng thái:

    ...
    onPressed: () {
         Navigator.of(context).pushNamedAndRemoveUntil('/', ModalRoute.withName('/'));
                },
    ...

0

Tóm lại, bạn nên làm cho widget xem trạng thái. Bạn cần sự quản lý của nhà nước đối với việc này.

Phương pháp của tôi dựa trên Nhà cung cấp được giải thích trong Mẫu kiến ​​trúc Flutter cũng như Tài liệu Flutter . Vui lòng tham khảo chúng để có lời giải thích ngắn gọn hơn nhưng ít nhiều các bước là:

  • Xác định mô hình trạng thái của bạn với các trạng thái mà tiện ích con cần quan sát.

Bạn có thể có nhiều trạng thái nói dataisLoadingchờ một số quy trình API. Mô hình tự mở rộng ChangeNotifier.

  • Bao bọc các widget phụ thuộc vào các trạng thái đó bằng lớp watcher.

Điều này có thể là Consumerhoặc Selector.

  • Khi bạn cần "tải lại", về cơ bản bạn cập nhật các trạng thái đó và phát các thay đổi.

Đối với mô hình trạng thái, lớp sẽ trông giống như sau. Chú ý đến việc notifyListenersphát sóng các thay đổi.

class DataState extends ChangeNotifier{

  bool isLoading;
  
  Data data;

  Future loadData(){
    isLoading = true;
    notifyListeners();

    service.get().then((newData){
      isLoading = false;
      data = newData;
      notifyListeners();
    });
  }
  
}

Bây giờ cho widget. Đây sẽ là một mã khung.

return ChangeNotifierProvider(

  create: (_) => DataState()..loadData(),
      
  child: ...{
    Selector<DataState, bool>(

        selector: (context, model) => model.isLoading,

        builder: (context, isLoading, _) {
          if (isLoading) {
            return ProgressBar;
          }

          return Container(

              child: Consumer<DataState>(builder: (context, dataState, child) {

                 return WidgetData(...);

              }
          ));
        },
      ),
  }
);

Phiên bản của mô hình trạng thái được cung cấp bởi ChangeNotifierProvider. Selector và người tiêu dùng xem các tiểu bang, mỗi cho isLoadingdatatương ứng. Không có nhiều sự khác biệt giữa chúng nhưng cá nhân bạn sử dụng chúng như thế nào sẽ phụ thuộc vào những gì mà nhà xây dựng của chúng cung cấp. Người tiêu dùng cung cấp quyền truy cập vào mô hình trạng thái để việc gọi loadDatađơn giản hơn đối với bất kỳ tiện ích con nào ngay bên dưới nó.

Nếu không thì bạn có thể sử dụng Provider.of. Nếu chúng tôi muốn làm mới trang khi trở lại từ màn hình thứ hai thì chúng tôi có thể làm như sau:

await Navigator.push(context, 
  MaterialPageRoute(
    builder: (_) {
     return Screen2();
));

Provider.of<DataState>(context, listen: false).loadData();

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.