Scaffold.of (), bir İskele içermeyen bir bağlamla çağrıldı


186

Gördüğünüz gibi düğmem Scaffold'un vücudunun içinde. Ama bu istisnayı anlıyorum:

Scaffold.of (), bir İskele içermeyen bir bağlamla çağrılır.

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

DÜZENLE:

Bu soruna başka bir çözüm buldum. Scaffold'a GlobalKey olan bir anahtar verirsek, SnackBar'ı vücudumuzu Builder widget'ı ile sarmaya gerek kalmadan aşağıdaki gibi görüntüleyebiliriz. İskele döndüren widget olsa da durum bilgisi olan widget olmalıdır:

 _scaffoldKey.currentState.showSnackBar(snackbar); 

Tüm uygulamanın içeriğini kolayca değiştirebilmesi veya kontrol edebilmesi için bir sarıcı yaptığı çok iyi bir öğretici buldum: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

Yanıtlar:


247

Bu istisna context, somutlaştırılan widget'ı kullandığınız için oluşur Scaffold. contextBir çocuğu değil Scaffold.

Bunu sadece farklı bir bağlam kullanarak çözebilirsiniz:

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

BuilderBurada kullanırken , farklı bir yol almanın tek yolu bu değildir BuildContext.

Alt ağacı farklı bir şekilde çıkarmak da mümkündür Widget(genellikle extract widgetrefactor kullanarak )


Bu istisna şu ile elde: setState () veya markNeedsBuild () derleme sırasında çağırdı. I / flutter (21754): Bu İskele widget'ı, çerçeve zaten I / flutter (21754): widget oluşturma işleminde olduğu için derlemeye ihtiyaç duyulamaz.
Figen Güngör

1
onPressedEğer geçirilen RaisedButtonbir işlev değildir. Değiştir() => _displaySnackBar(context)
Rémi Rousselet

Anladım: onPressed: () {_displaySnackBar (içerik);}, Teşekkürler =)
Figen Güngör

1
Ben .of (bağlam) ile verilen türden biri ile karşılaşana kadar widget hiyerarşisi kadar seyahat gerekiyordu, bu durumda, İskele "Widget inşa (.." ulaşmadan önce karşılaşması gerekir. Bu şekilde?
CodeGrue

@ RémiRousselet, Flutter'da kaç tür içerik var? Dediğin gibi widget içeriği, bir iskele alt yapısı.
CopsOnRoad

121

Kullanabilirsiniz GlobalKey. Tek dezavantajı, GlobalKey'i kullanmanın bunu yapmanın en etkili yolu olmayabilir.

Bu konuda iyi bir şey, bu anahtarı herhangi bir iskele içermeyen diğer özel widget sınıfına da aktarabilmenizdir. (Bkz buraya )

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

Teşekkürler Lebohang Mbele
Geliştirici

1
Ağacınız birden çok dosyada tanımlanan birden çok widget'tan oluştuğunda bunu nasıl çoğaltabileceğini sorabilir miyim? _scaffoldKeyönde gelen alt çizgi nedeniyle özeldir. Ancak olmasa bile, HomePagesınıfın içindedir ve bu nedenle ağacın bir yerinde yeni bir HomePage somutlaştırmadan, görünüşte dairesel bir referans oluşturmadan kullanılamaz.
ExactaBox

1
kendi soruma cevap vermek için: bir özellikli bir singleton sınıfı oluşturun GlobalKey<ScaffoldState>, singleton'un anahtar özelliğini ayarlamak için iskele ile Widget yapıcısını düzenleyin, sonra çocuk widget'ın ağacında şöyle bir şey çağırabiliriz mySingleton.instance.key.currentState.showSnackBar()...
ExactaBox

Bu neden verimsiz?
user2233706

@ user2233706 Bu GlobalKey ait belgelerde açıklanan burada . Ayrıca bu yazı daha fazla ışık tutabilir.
Lebohang Mbele

43

Bu sorunu çözmenin iki yolu

1) Oluşturucu widget'ını kullanma

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 Kullanımı

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

Yöntemin belgelerinden bunu kontrol edin:

İskele aslında aynı oluşturma işlevinde oluşturulduğunda, yapı işlevine ilişkin bağlam bağımsız değişkeni, İskele'yi bulmak için kullanılamaz (çünkü döndürülen widget'ın "üstünde"). Bu gibi durumlarda, bir Yapılandırıcı ile aşağıdaki teknik, Yapı İskelesi ile İskele "altında" yeni bir kapsam sağlamak için kullanılabilir:

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

Açıklamasını docs yönteminden kontrol edebilirsiniz


13

Bu sorunu çözmenin basit yolu, aşağıdaki kodla bu final gibi iskele için bir anahtar oluşturmak olacaktır:

İlk: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Scecond: Anahtarı İskele'nize atayın key: _scaffoldKey

Üçüncü olarak: Snackbar'ı _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));


3

Yaşadığınız davranış, Flutter belgelerinde "zor durumda" olarak da adlandırılır .

Nasıl düzeltilir

Burada yayınlanan diğer yanıtlardan da görebileceğiniz gibi, sorun farklı şekillerde düzeltildi. Örneğin, atıfta bulunduğum dokümantasyon, sorunu Builderoluşturan bir

bir iç BuildContextve böylece onPressedyöntemleri başvurabilir Scaffoldile Scaffold.of().

Böylece çağrı için bir yol showSnackBargelen Scaffold olurdu

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

Şimdi meraklı okuyucu için biraz detay

Ben sadece ( Android Studio ) bir kod parçası ( Flutter sınıfı, yöntemi, vb) imleci ayarlamak ve o belirli bir parça için belgeleri göstermek için ctrl + B tuşuna basarak Flutter belgelerini keşfetmek için oldukça öğretici buldum .

Karşılaştığınız belirli sorun , okunabilecek BuildContext belgesinde belirtilmiştir

Her widget'in kendi BuildContext'i vardır ve bu [[]] build işlevi tarafından döndürülen widget'ın üst öğesi olur.

Yani, bizim durumda bu araçlar bağlamda olacak bizim İskele widget ebeveyn oluşturulduğu (!). Ayrıca, Scaffold.of için docu geri döndüğünü söylüyor

Bu sınıfın verilen bağlamı içine alan en yakın [ İskele ] örneğindeki durum.

Ancak bizim durumumuzda, bağlam (henüz) bir İskele içermez (henüz inşa edilmemiştir). Builder'ın devreye girdiği yer burada !

Bir kez daha, belge bizi aydınlatıyor. Orada okuyabiliriz

[Builder sınıfı, basittir] Alt parçasını almak için bir kapatma çağrısı yapan platonik bir widget.

Hey, bir dakika, ne !? Tamam, itiraf ediyorum: bu çok yardımcı olmuyor ... Ama ( başka bir SO iş parçacığının ardından ) söylemek yeterli

Amacı Oluşturucu sınıfının inşa ve dönüş çocuk widget'lar için basitçe.

Şimdi her şey netleşiyor! Scaffold'un içindeki Builder'ı çağırarak , kendi bağlamını elde edebilmek için Scaffold'u inşa ediyoruz ve bu innerContext ile donanmış olarak nihayet Scaffold.of (innerContext) diyebiliriz.

Yukarıdaki kodun açıklamalı sürümü aşağıdaki gibidir

@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

Varsayılan çerez çubuğunu kullanmakla uğraşmazdım, çünkü daha fazla özelleştirilebilirlik sağlayan bir flushbar paketi içe aktarabilirsiniz:

https://pub.dev/packages/flushbar

Örneğin:

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

0

Daha etkili bir çözüm, oluşturma işlevinizi birkaç widget'a bölmektir. Bu, İskele alabileceğiniz 'yeni bir bağlam' sunar

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

Snackbar görüntüleyecek düğme widget'ınızı ayıklayın.

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

Yaklaşımınız mevcut cevaplarla nasıl karşılaştırılır? Bu yaklaşımı, örneğin kabul edilen cevaba göre seçmek için bir neden var mı?
Jeremy Caney

0

burada snackbar'a ihtiyacımız olan başka bir widget'ı sarmak için bir oluşturucu kullanıyoruz

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.