Scaffold.of () एक संदर्भ के साथ कहा जाता है जिसमें एक पाड़ नहीं होता है


184

जैसा कि आप देख सकते हैं कि मेरा बटन स्कैफोल्ड के शरीर के अंदर है। लेकिन मुझे यह अपवाद मिलता है:

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

संपादित करें:

मुझे इस समस्या का एक और समाधान मिला। यदि हम स्कैफोल्ड को एक कुंजी देते हैं जो GlobalKey है, तो हम SnackBar को बिल्डर विजेट के साथ हमारे शरीर को लपेटने की आवश्यकता के बिना निम्नलिखित प्रदर्शित कर सकते हैं। विजेट जो पाड़ वापस करता है, हालांकि स्टेटफुल विजेट होना चाहिए:

 _scaffoldKey.currentState.showSnackBar(snackbar); 

मुझे एक बहुत अच्छा ट्यूटोरियल मिला जहां उन्होंने एक रैपर बनाया ताकि वह आसानी से पूरे ऐप के संदर्भ को बदल सके या नियंत्रित कर सके: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

जवाबों:


245

यह अपवाद इसलिए होता है क्योंकि आप contextउस विजेट का उपयोग कर रहे हैं जो तात्कालिक है Scaffold। के contextबच्चे का नहीं Scaffold

आप इसे एक अलग संदर्भ का उपयोग करके हल कर सकते हैं:

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रिफ्लेक्टर का उपयोग करके )


मुझे इसके साथ यह अपवाद मिलता है: buildState () या markNeedsBuild () बिल्ड के दौरान कहा जाता है। I / स्पंदन (21754): इस स्कैफोल्ड विजेट को बनाने की आवश्यकता के रूप में चिह्नित नहीं किया जा सकता क्योंकि फ्रेम पहले से ही I / flutter (21754) में है: विजेट बनाने की प्रक्रिया।
फिजेन गुंगोले

1
onPressedआप के लिए पारित RaisedButtonएक समारोह नहीं है। इसे बदल कर() => _displaySnackBar(context)
Rémi Rousselet

पकड़ लिया: onPressed: () {_displaySnackBar (संदर्भ);}, धन्यवाद =)
Figen Güngör

1
मैंने सोचा था कि .of (संदर्भ) यह विजेट पदानुक्रम तक यात्रा करने वाला है, जब तक कि यह दिए गए प्रकार में से एक का सामना नहीं करता है, इस मामले में, स्कैफोल्ड, जिसे इसे "विजेट बिल्ड (..") तक पहुंचने से पहले सामना करना चाहिए। इस तरह से?
CodeGrue

@ RémiRousselet, स्पंदन में कितने प्रकार के संदर्भ हैं? जैसा आपने कहा कि विजेट का संदर्भ, पाड़ के बच्चे का संदर्भ।
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
मेरे अपने प्रश्न का उत्तर देने के लिए: एक GlobalKey<ScaffoldState>संपत्ति के साथ एक सिंगलटन क्लास बनाएं , सिंगलटन की प्रमुख संपत्ति को सेट करने के लिए मचान के साथ विजेट के निर्माता को संपादित करें, फिर बच्चे के विजेट के पेड़ में हम कुछ ऐसा कह सकते हैं mySingleton.instance.key.currentState.showSnackBar()...
ExactaBox

यह अक्षम क्यों है?
user2233706

@ user2233706 यह GlobalKey के प्रलेखन में यहाँ उल्लेख किया गया है । इसके अलावा इस पोस्ट को और अधिक प्रकाश डाला जा सकता है।
लीबॉन्ग मैबल

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

विधि के प्रलेखन से इसे जांचें:

जब स्कैफोल्ड वास्तव में एक ही बिल्ड फ़ंक्शन में बनाया जाता है, तो बिल्ड फ़ंक्शन के संदर्भ तर्क का उपयोग स्कैफोल्ड को खोजने के लिए नहीं किया जा सकता है (क्योंकि यह विजेट वापस "ऊपर" है)। ऐसे मामलों में, बिल्डर के साथ निम्नलिखित तकनीक का उपयोग बिल्डकॉनटेक्स्ट के साथ एक नया दायरा प्रदान करने के लिए किया जा सकता है जो कि "मचान" के अंतर्गत है:

@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

तीसरा: स्नैकबार का उपयोग करके कॉल करें _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));


3

जो व्यवहार आप अनुभव कर रहे हैं वह यहां तक ​​कि फ़्लटर प्रलेखन में "ट्रिकी केस" के रूप में जाना जाता है ।

कैसे ठीक करना है

मुद्दा अलग-अलग तरीकों से तय किया गया है जैसा कि आप यहां पोस्ट किए गए अन्य उत्तरों से देख सकते हैं। उदाहरण के लिए, दस्तावेज़ीकरण का संदर्भ मैं इस बात का हल करता हूं कि Builderकौन सा सृजन करता है

एक आंतरिक BuildContextताकि onPressedविधियों के Scaffoldसाथ उल्लेख कर सकें Scaffold.of()

इस प्रकार मचान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.')
            ));
          }
        );
      }
    )
  );
}

अब जिज्ञासु पाठक के लिए कुछ विस्तार से

मैं अपने आप को पता लगाने के लिए काफी शिक्षाप्रद पाया स्पंदन बस (द्वारा प्रलेखन एंड्रॉयड स्टूडियो कोड के एक टुकड़े पर कर्सर की स्थापना) ( स्पंदन क्लास, विधि, आदि) और उस विशिष्ट टुकड़े के लिए प्रलेखन दिखाने के लिए ctrl + B दबाया।

आप जिस विशेष समस्या का सामना कर रहे हैं, उसका उल्लेख बिल्डकॉनटेक्स्ट के लिए दी गई है , जहां पढ़ा जा सकता है

प्रत्येक विजेट का अपना BuildContext होता है , जो [...] द्वारा बनाए गए विजेट का जनक बन जाता है।

तो, इसका मतलब है कि हमारे मामले में संदर्भ हमारे मचान विजेट के माता-पिता होंगे जब इसे बनाया जाता है (!)। इसके अलावा, Scaffold.of के लिए यह कहता है कि यह रिटर्न करता है

इस वर्ग के निकटतम [ पाड़ ] उदाहरण से राज्य जो दिए गए संदर्भ को घेरता है।

लेकिन हमारे मामले में, संदर्भ एक स्कैफोल्ड को संलग्न नहीं करता है (अभी तक) (यह अभी तक नहीं बनाया गया है)। वहाँ है जहाँ बिल्डर कार्रवाई में आता है!

एक बार फिर, मुद्रा हमें प्रकाशित करती है। वहां हम पढ़ सकते हैं

[बिल्डर वर्ग, बस है] एक प्लेटोनिक विजेट जो अपने बच्चे के विजेट को प्राप्त करने के लिए एक बंद कॉल करता है।

अरे, एक पल रुको, क्या ?! ठीक है, मैं मानता हूं: यह बहुत मदद नहीं कर रहा है ... लेकिन यह कहना पर्याप्त है ( एक और SO थ्रेड के बाद )

के प्रयोजन के बिल्डर वर्ग का निर्माण करेंगे और इसके बदले बच्चे विगेट्स प्रदान करना है।

तो अब यह सब स्पष्ट हो गया है! मचान के अंदर बिल्डर को बुलाकर हम पाड़ बांध रहे हैं ताकि अपना स्वयं का संदर्भ प्राप्त करने में सक्षम हो, और उस आंतरिक संदर्भ से लैस होकर हम अंततः कॉल कर सकें। स्कैफोल्ड

ऊपर दिए गए कोड का एक एनोटेट संस्करण

@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

एक अधिक कुशल समाधान अपने निर्माण कार्य को कई विजेट में विभाजित करना है। यह एक 'नया संदर्भ' पेश करता है, जिससे आप पाड़ पा सकते हैं

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

आपका दृष्टिकोण मौजूदा उत्तरों की तुलना कैसे करता है? क्या इस दृष्टिकोण को चुनने का एक कारण है, कहते हैं, स्वीकृत उत्तर?
जेरेमी कैनी

0

यहां हम एक बिल्डर को दूसरे विजेट में लपेटने के लिए उपयोग करते हैं जहां हमें स्नैकबार की आवश्यकता होती है

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.