Scaffold.of () ถูกเรียกด้วยบริบทที่ไม่มี Scaffold


186

อย่างที่คุณเห็นปุ่มของฉันอยู่ในร่างกายของ Scaffold แต่ฉันได้รับข้อยกเว้นนี้:

Scaffold.of () ถูกเรียกด้วยบริบทที่ไม่มี Scaffold

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 เราสามารถแสดง SnackBar ดังต่อไปนี้โดยไม่จำเป็นต้องห่อตัวร่างกายของเราด้วยวิดเจ็ต Builder วิดเจ็ตที่ให้ Scaffold กลับมาควรเป็นวิดเจ็ต Stateful

 _scaffoldKey.currentState.showSnackBar(snackbar); 

ฉันพบการสอนที่ดีมากที่เขาใช้กระดาษห่อเพื่อให้เขาสามารถเปลี่ยนหรือควบคุมบริบทของแอปทั้งหมดได้อย่างง่ายดาย: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

คำตอบ:


247

ข้อยกเว้นนี้เกิดขึ้นเพราะคุณกำลังใช้contextของเครื่องมือที่ Scaffoldinstantiated ไม่ได้เป็นของเด็กของcontextScaffold

คุณสามารถแก้ปัญหานี้ได้โดยใช้บริบทอื่น:

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'),
            ),
        ),
    ),
);

ทราบว่าในขณะที่เรากำลังใช้ที่นี่นี้ไม่ได้เป็นวิธีเดียวที่จะได้รับที่แตกต่างกันBuilderBuildContext

นอกจากนี้ยังเป็นไปได้ที่จะแยกทรีย่อยออกเป็นต่าง ๆWidget(โดยปกติจะใช้extract widgetrefactor)


ฉันได้รับข้อยกเว้นนี้: setState () หรือ markNeedsBuild () เรียกว่าในระหว่างการสร้าง I / flutter (21754): วิดเจ็ต Scaffold นี้ไม่สามารถทำเครื่องหมายว่าจำเป็นต้องสร้างเนื่องจากเฟรมเวิร์กอยู่ใน I / flutter (21754): กระบวนการสร้างวิดเจ็ต
Figen Güngör

1
onPressedคุณส่งผ่านไปยังRaisedButtonไม่ได้ฟังก์ชั่น เปลี่ยนเป็น() => _displaySnackBar(context)
Rémi Rousselet

Gotcha: onPressed: () {_displaySnackBar (บริบท);}, ขอบคุณ =)
Figen Güngör

1
ฉันคิดว่าด้วย. ของ (บริบท) มันควรจะเดินทางถึงลำดับชั้นของเครื่องมือจนกว่าจะพบหนึ่งในประเภทที่กำหนดในกรณีนี้ Scaffold ซึ่งมันควรจะพบก่อนที่จะถึง "Widget build (.. " มันไม่ทำงาน ทางนี้หรือเปล่า
CodeGrue

@ RémiRousseletบริบทมีกี่ประเภทใน Flutter เช่นเดียวกับที่คุณพูดถึงบริบทของวิดเจ็ตบริบทของลูกของ Scaffold
CopsOnRoad

121

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
  }
}

ขอบคุณ Lebohang Mbele
นักพัฒนา

1
ฉันจะถามวิธีการทำซ้ำสิ่งนี้ได้อย่างไรเมื่อทรีของคุณประกอบด้วยหลายวิดเจ็ตที่กำหนดในหลายไฟล์? _scaffoldKeyเป็นส่วนตัวเนื่องจากขีดเส้นใต้นำ แต่แม้ว่ามันจะไม่ได้อยู่ในHomePageชั้นเรียนดังนั้นจึงไม่สามารถใช้งานได้โดยไม่ต้องสร้างหน้าแรกใหม่ให้อยู่ที่ใดที่หนึ่งบนต้นไม้ดูเหมือนว่าจะสร้างการอ้างอิงแบบวงกลม
ExactaBox

1
เพื่อตอบคำถามของฉัน: สร้างคลาส singleton ด้วยGlobalKey<ScaffoldState>คุณสมบัติแก้ไข constructor ของ Widget ด้วย scaffold เพื่อตั้งค่าคุณสมบัติหลักของ singleton จากนั้นในทรีของวิดเจ็ตลูกเราสามารถเรียกบางอย่างเช่นmySingleton.instance.key.currentState.showSnackBar()...
ExactaBox

ทำไมไม่มีประสิทธิภาพนี้
user2233706

@ user2233706 นี้ถูกกล่าวถึงในเอกสารของ GlobalKey ที่นี่ นอกจากนี้โพสต์นี้อาจมีแสงมากขึ้น
Lebohang Mbele

43

สองวิธีในการแก้ปัญหานี้

1) การใช้วิดเจ็ตเครื่องมือสร้าง

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) การใช้ GlobalKey

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);
          },
        ),
     );
   }
}

16

ตรวจสอบจากเอกสารวิธีการ:

เมื่อ Scaffold ถูกสร้างขึ้นในฟังก์ชัน build เดียวกันอาร์กิวเมนต์บริบทของฟังก์ชัน build ไม่สามารถใช้เพื่อค้นหา Scaffold (เนื่องจากเป็น "เหนือ" วิดเจ็ตที่ส่งคืน) ในกรณีดังกล่าวสามารถใช้เทคนิคต่อไปนี้กับตัวสร้างเพื่อจัดเตรียมขอบเขตใหม่ด้วย BuildContext ที่เป็น "ภายใต้" Scaffold:

@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: () {
              Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('Hello!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

คุณสามารถตรวจสอบคำอธิบายจากเอกสารวิธีการ


13

วิธีง่ายๆในการแก้ไขปัญหานี้คือการสร้างรหัสสำหรับนั่งร้านของคุณเช่นนี้ด้วยรหัสต่อไปนี้:

ครั้งแรก: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Scecond: กำหนดรหัสให้กับนั่งร้านของคุณ key: _scaffoldKey

ประการที่สาม: เรียกใช้ Snackbar โดยใช้ _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));


3

พฤติกรรมที่คุณกำลังประสบจะเรียกว่าแม้จะเป็น "หากินกรณี" ในFlutter เอกสาร

วิธีแก้ไข

ปัญหาได้รับการแก้ไขในรูปแบบที่แตกต่างกันไปตามที่คุณเห็นจากคำตอบอื่น ๆ ที่โพสต์ไว้ที่นี่ ตัวอย่างเช่นชิ้นส่วนของเอกสารฉันอ้างถึงการแก้ปัญหาโดยใช้สิ่งBuilderที่สร้างขึ้น

ภายในBuildContextเพื่อให้onPressedวิธีการสามารถดูได้ด้วยScaffoldScaffold.of()

ดังนั้นวิธีการโทรshowSnackBarจากScaffoldจะเป็น

@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.')
            ));
          }
        );
      }
    )
  );
}

ตอนนี้รายละเอียดบางอย่างสำหรับผู้อ่านอยากรู้อยากเห็น

ฉันพบว่าค่อนข้างให้คำแนะนำในการสำรวจเอกสารFlutterโดยเพียง ( Android Studio ) การตั้งค่าเคอร์เซอร์บนชิ้นส่วนของรหัส ( คลาสFlutter , วิธีการ ฯลฯ ) และกด ctrl + B เพื่อแสดงเอกสารสำหรับชิ้นส่วนนั้น

ปัญหาเฉพาะที่คุณกำลังเผชิญอยู่ถูกกล่าวถึงใน docu สำหรับBuildContextซึ่งสามารถอ่านได้

แต่ละวิดเจ็ตมีBuildContextของตัวเองซึ่งกลายเป็นพาเรนต์ของวิดเจ็ตที่ส่งคืนโดย [... ]

ดังนั้นนี่หมายความว่าในบริบท กรณีของเราจะเป็นพาเรนต์ของวิดเจ็ต Scaffold ของเราเมื่อมันถูกสร้างขึ้น (!) เพิ่มเติม docu สำหรับScaffold.ofบอกว่ามันจะกลับมา

สถานะจากอินสแตนซ์[ Scaffold ] ที่ใกล้เคียงที่สุดของคลาสนี้ที่ล้อมรอบบริบทที่กำหนด

แต่ในกรณีของเราบริบทไม่ได้แนบ (ยัง) นั่งร้าน (แต่ยังไม่ได้สร้าง) มีที่มาของBuilder !

อีกครั้ง docu สว่างเรา ที่นั่นเราสามารถอ่าน

[คลาส Builder เป็นเพียง] วิดเจ็ตสงบที่เรียกว่าการปิดเพื่อรับวิดเจ็ตลูก

เดี๋ยวก่อนเดี๋ยวก่อนสิ! ตกลงฉันยอมรับ: นั่นไม่ได้ช่วยมาก ... แต่มันก็เพียงพอที่จะบอกว่า (ตามหัวข้อ SO อื่น ) ที่

จุดประสงค์ของคลาสBuilderคือเพื่อสร้างและส่งคืนวิดเจ็ตชายด์

ดังนั้นตอนนี้ทุกอย่างชัดเจน! ด้วยการเรียกBuilderภายในScaffoldเรากำลังสร้าง 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.')
            ));
          }
        );
      }
    )
  );
}

1

ฉันไม่ต้องกังวลกับการใช้สแน็คบาร์เริ่มต้นเพราะคุณสามารถนำเข้าแพ็คเกจฟลัชบาร์ซึ่งทำให้สามารถปรับแต่งได้มากขึ้น:

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);

0

ทางออกที่มีประสิทธิภาพมากขึ้นคือการแบ่งฟังก์ชั่นการสร้างของคุณออกเป็นหลาย ๆ วิดเจ็ต นี่เป็นการแนะนำ 'บริบทใหม่' ซึ่งคุณสามารถขอรับ 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'),
              ),
            );
          }),
    );
  }
}

0

แตกวิดเจ็ตปุ่มของคุณซึ่งจะแสดงแถบสแน็คบาร์

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'),
                  );
  }
}

วิธีการของคุณเปรียบเทียบกับคำตอบที่มีอยู่ได้อย่างไร มีเหตุผลที่จะเลือกวิธีนี้มากกว่าพูดคำตอบที่ยอมรับหรือไม่?
Jeremy Caney

0

ที่นี่เราใช้เครื่องมือสร้างเพื่อห่อวิดเจ็ตอื่นที่เราต้องการสแน็คบาร์

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.