簡體   English   中英

使用不包含腳手架的上下文調用的 Scaffold.of()

[英]Scaffold.of() called with a context that does not contain a Scaffold

如您所見,我的按鈕位於Scaffold的體內。 但我得到這個例外:

Scaffold.of() 在不包含腳手架的上下文中調用。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('SnackBar Playground'),
      ),
      body: Center(
        child: RaisedButton(
          color: Colors.pink,
          textColor: Colors.white,
          onPressed: _displaySnackBar(context),
          child: Text('Display SnackBar'),
        ),
      ),
    );
  }
}

_displaySnackBar(BuildContext context) {
  final snackBar = SnackBar(content: Text('Are you talkin\' to me?'));
  Scaffold.of(context).showSnackBar(snackBar);
}

編輯:

我找到了解決這個問題的另一種方法。 如果我們給Scaffold一個鍵,它是GlobalKey<ScaffoldState> ,我們可以如下顯示 SnackBar,而不需要將我們的主體包裝在Builder小部件中。 不過,返回Scaffold的小部件應該是有狀態的小部件。

 _scaffoldKey.currentState.showSnackBar(snackbar); 

發生此異常是因為您正在使用實例化Scaffold的小部件的context 不是Scaffold的孩子的context

您可以通過使用不同的上下文來解決這個問題:

Scaffold(
    appBar: AppBar(
        title: Text('SnackBar Playground'),
    ),
    body: Builder(
        builder: (context) => 
            Center(
            child: RaisedButton(
            color: Colors.pink,
            textColor: Colors.white,
            onPressed: () => _displaySnackBar(context),
            child: Text('Display SnackBar'),
            ),
        ),
    ),
);

請注意,雖然我們在這里使用Builder ,但這並不是獲取不同BuildContext的唯一方法。

也可以將子樹提取到不同的Widget (通常使用extract widget refactor)

您可以使用GlobalKey 唯一的缺點是使用 GlobalKey 可能不是最有效的方法。

這樣做的一個好處是,您還可以將此密鑰傳遞給不包含任何腳手架的其他自定義小部件類。 見(這里

class HomePage extends StatelessWidget {
  final _scaffoldKey = GlobalKey<ScaffoldState>(); \\ new line
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: _scaffoldKey,                           \\ new line
      appBar: AppBar(
        title: Text('SnackBar Playground'),
      ),
      body: Center(
        child: RaisedButton(
          color: Colors.pink,
          textColor: Colors.white,
          onPressed: _displaySnackBar(context),
          child: Text('Display SnackBar'),
        ),
      ),
    );
  }
  _displaySnackBar(BuildContext context) {
    final snackBar = SnackBar(content: Text('Are you talkin\' to me?'));
    _scaffoldKey.currentState.showSnackBar(snackBar);   \\ edited line
  }
}

您可以通過兩種方式解決此問題:

1) 使用 Builder 小部件

Scaffold(
    appBar: AppBar(
        title: Text('My Profile'),
    ),
    body: Builder(
        builder: (ctx) => RaisedButton(
            textColor: Colors.red,
            child: Text('Submit'),
            onPressed: () {
                 Scaffold.of(ctx).showSnackBar(SnackBar(content: Text('Profile Save'),),);
            }               
        ),
    ),
);

2) 使用全局鍵

class HomePage extends StatelessWidget {
  
  final globalKey = GlobalKey<ScaffoldState>();
  
  @override
  Widget build(BuildContext context) {
     return Scaffold(
       key: globalKey,
       appBar: AppBar(
          title: Text('My Profile'),
       ),
       body:  RaisedButton(
          textColor: Colors.red,
          child: Text('Submit'),
          onPressed: (){
               final snackBar = SnackBar(content: Text('Profile saved'));
               globalKey.currentState.showSnackBar(snackBar);
          },
        ),
     );
   }
}

更新 - 2021

不推薦使用Scaffold.of(context)以支持ScaffoldMessenger

從方法文檔中檢查這一點:

ScaffoldMessenger現在處理SnackBars以便跨路由持久化並始終顯示在當前 Scaffold 上。 默認情況下, MaterialApp 中包含根ScaffoldMessenger ,但您可以為ScaffoldMessenger創建自己的受控范圍,以進一步控制哪些 Scaffolds 接收您的 SnackBar。

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Demo')
    ),
    body: Builder(
      // Create an inner BuildContext so that the onPressed methods
      // can refer to the Scaffold with Scaffold.of().
      builder: (BuildContext context) {
         return Center(
          child: RaisedButton(
            child: Text('SHOW A SNACKBAR'),
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                content: Text('Hello!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

您可以在此處查看詳細的棄用和新方法:

解決此問題的簡單方法是使用以下代碼為您的腳手架創建一個密鑰,如最終版本:

第一: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Scecond:將密鑰分配給您的 Scaffold key: _scaffoldKey

第三:調用 Snackbar 使用_scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

2021 年 3 月 3 日更新:Flutter 2.0 剛剛發布...

(就歷史價值而言,我還進一步低於我的原始答案......)


現在...輸入...

腳手架信使

從我們閱讀的文檔中

Scaffold 中的 SnackBar API 現在由 ScaffoldMessenger 處理,其中之一在 MaterialApp 的上下文中默認可用

因此,現在使用全新的ScaffoldMessenger,您將能夠編寫如下代碼

Scaffold(
  key: scaffoldKey,
  body: GestureDetector(
    onTap: () {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
        content: const Text('snack'),
        duration: const Duration(seconds: 1),
        action: SnackBarAction(
          label: 'ACTION',
          onPressed: () { },
        ),
      ));
    },
    child: const Text('SHOW SNACK'),
  ),
);

現在,再次在文檔中我們可以看到

在過渡期間呈現 SnackBar 時,SnackBar 會完成一個 Hero 動畫,平滑地移動到下一頁。

ScaffoldMessenger 創建了一個作用域,所有后代 Scaffolds 在其中注冊以接收 SnackBars,這就是它們在這些轉換中持久化的方式。 當使用 MaterialApp 提供的根 ScaffoldMessenger 時,所有后代 Scaffolds 都會收到 SnackBars,除非在樹的更下方創建新的 ScaffoldMessenger 作用域。 通過實例化您自己的 ScaffoldMessenger,您可以控制哪些 Scaffolds 接收 SnackBars,哪些不基於您的應用程序的上下文。


原答案

您所遇到的這種行為甚至在Flutter文檔中被稱為“棘手案例”。

怎么修

正如您可以從此處發布的其他答案中看到的那樣,該問題已以不同方式解決。 例如,一條文檔編制的我用指解決問題的Builder ,其產生

BuildContext使得onPressed方法可以參照ScaffoldScaffold.of()

因此,從Scaffold調用showSnackBar的方法是

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('Demo')),
    body: Builder(
      builder: (BuildContext innerContext) {
        return FlatButton(
          child: Text('BUTTON'),
          onPressed: () {
            Scaffold.of(innerContext).showSnackBar(SnackBar(
              content: Text('Hello.')
            ));
          }
        );
      }
    )
  );
}

現在為好奇的讀者提供一些細節

我自己發現通過簡單地 ( Android Studio ) 將光標設置在一段代碼( Flutter類、方法等)上並按 ctrl+B 以顯示該特定部分的文檔,我自己發現探索Flutter文檔非常有指導意義。

BuildContext的文檔中提到了您面臨的特定問題,可以在其中閱讀

每個小部件都有自己的BuildContext ,它成為 [...].build 函數返回的小部件的父級。

因此,這意味着在我們的案例中,上下文是創建時我們的 Scaffold 小部件的父級(!)。 此外, Scaffold.of的文檔說它返回

來自包含給定上下文的此類的最近 [ Scaffold ] 實例的狀態。

但是在我們的例子中,上下文沒有(還)包含一個腳手架(它還沒有被構建)。 Builder在那里開始行動了!

文檔再一次照亮了我們。 在那里我們可以閱讀

[Builder 類,很簡單] 一個柏拉圖式的小部件,它調用一個閉包來獲取它的子小部件。

嘿,等一下,什么!? 好吧,我承認:這並沒有多大幫助......但足以說(遵循另一個 SO 線程

Builder類的目的只是構建和返回子小部件。

所以現在一切都清楚了! 通過在Scaffold 中調用Builder ,我們正在構建 Scaffold 以便能夠獲得它自己的上下文,並且有了這個innerContext我們最終可以調用Scaffold.of(innerContext)

上面代碼的注釋版本如下

@override
Widget build(BuildContext context) {
  // here, Scaffold.of(context) returns null
  return Scaffold(
    appBar: AppBar(title: Text('Demo')),
    body: Builder(
      builder: (BuildContext innerContext) {
        return FlatButton(
          child: Text('BUTTON'),
          onPressed: () {
            // here, Scaffold.of(innerContext) returns the locally created Scaffold
            Scaffold.of(innerContext).showSnackBar(SnackBar(
              content: Text('Hello.')
            ));
          }
        );
      }
    )
  );
}

使用ScaffoldMessenger (推薦)

var snackBar = SnackBar(content: Text('Hi there'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);

示例(沒有BuilderGlobalKey

Scaffold(
  body: ElevatedButton(
    onPressed: () {
      var snackBar = SnackBar(content: Text('Hello World'));
      ScaffoldMessenger.of(context).showSnackBar(snackBar);
    },
    child: Text('Show SnackBar'),
  ),
)

從 Flutter 版本 1.23-18.1.pre 開始,您可以使用 ScaffoldMessenger

final mainScaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();

class Main extends StatelessWidget {
  @override
  Widget build(BuildContext) {
    return MaterialApp(
      ...
      scaffoldMessengerKey: mainScaffoldMessengerKey
      ...
    );
  }
}

應用程序內的某處:

mainScaffoldMessengerKey.currentState.showSnackBar(Snackbar(...));

更有效的解決方案是將您的構建功能拆分為多個小部件。 這引入了一個“新上下文”,您可以從中獲得 Scaffold

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Scaffold.of example.')),
        body: MyScaffoldBody(),
      ),
    );
  }
}

class MyScaffoldBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
          child: Text('Show a snackBar'),
          onPressed: () {
            Scaffold.of(context).showSnackBar(
              SnackBar(
                content: Text('Have a Snack'),
              ),
            );
          }),
    );
  }
}

我不會費心使用默認的小吃店,因為您可以導入一個flushbar 包,這可以實現更大的可定制性:

https://pub.dev/packages/flushbar

例如:

Flushbar(
                  title:  "Hey Ninja",
                  message:  "Lorem Ipsum is simply dummy text of the printing and typesetting industry",
                  duration:  Duration(seconds: 3),              
                )..show(context);

我可能遲到了。 但這也會幫助某人。 Scaffold下添加一個_key 然后使用該_key調用openDrawer方法。

return Scaffold(
  key: _scaffoldKey, //this is the key
  endDrawer: Drawer(),
  appBar: AppBar( 
//all codes for appbar here
actions: [
IconButton(
        splashRadius: 20,
        icon: Icon(Icons.settings),
        onPressed: () {
          _scaffoldKey.currentState.openEndDrawer(); // this is it
       
        },
      ),]

提取將顯示小吃欄的按鈕小部件。

class UsellesslyNestedButton extends StatelessWidget {
  const UsellesslyNestedButton({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      onPressed: (){
        showDefaultSnackbar(context);
                    },
                    color: Colors.blue,
                    child: Text('Show about'),
                  );
  }
}

在這里,我們使用構建器將另一個小部件包裝在我們需要小吃店的地方

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
           Expanded(
              child: Container(
                width: MediaQuery.of(context).size.width,
                height: MediaQuery.of(context).size.height,
                child: Builder(
                  builder: (context) => RaisedButton(
                    onPressed: () {
                        final snackBar = SnackBar(
                          content: Text("added to cart"),
                           action: SnackBarAction(
                            label: 'Undo',
                             onPressed: () {
                              // Some code to undo the change.
                            },
                          ),
                        );
                    },
                    textColor: Colors.white,
                    color: Colors.pinkAccent,
                    child: Text("Add To Cart",
                        style: TextStyle(
                            fontSize: 18, fontWeight: FontWeight.w600)),
                  ),
                ),
              ),
            )

也許這段代碼中有一個解決方案。

ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("SnackBar Message")));

試試這個代碼:

Singleton.showInSnackBar(
    Scaffold.of(context).context, "Theme Changed Successfully");
// Just use Scaffold.of(context) before context!!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM