Scaffold.of () dipanggil dengan konteks yang tidak mengandung Scaffold

184

Seperti yang Anda lihat tombol saya ada di dalam tubuh Scaffold. Tapi saya mendapatkan pengecualian ini:

Scaffold.of () dipanggil dengan konteks yang tidak mengandung 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);
}

EDIT:

Saya menemukan solusi lain untuk masalah ini. Jika kita memberikan kunci Scaffold yang merupakan GlobalKey, kita dapat menampilkan SnackBar sebagai berikut tanpa perlu membungkus tubuh kita dengan widget Builder. Widget yang mengembalikan Scaffold harus menjadi widget Stateful .:

 _scaffoldKey.currentState.showSnackBar(snackbar); 
Figen Güngör
sumber
Saya menemukan tutorial yang sangat bagus di mana dia membuat pembungkus sehingga dia dapat dengan mudah mengubah atau mengontrol konteks seluruh aplikasi: noobieprogrammer.blogspot.com/2020/06/…
doppelgunner

Jawaban:

245

Pengecualian ini terjadi karena Anda menggunakan contextwidget yang dipakai Scaffold. Bukan contextanak dari Scaffold.

Anda dapat menyelesaikan ini hanya dengan menggunakan konteks yang berbeda:

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

Perhatikan bahwa saat kami menggunakan di Buildersini, ini bukan satu-satunya cara untuk mendapatkan yang berbeda BuildContext.

Dimungkinkan juga untuk mengekstrak subtree menjadi berbeda Widget(biasanya menggunakan extract widgetrefactor)

Rémi Rousselet
sumber
Saya mendapatkan pengecualian ini dengan ini: setState () atau markNeedsBuild () dipanggil saat membangun. I / flutter (21754): Widget Scaffold ini tidak dapat ditandai sebagai harus dibangun karena kerangka kerja sudah ada dalam I / flutter (21754): proses pembuatan widget.
Figen Güngör
1
Yang onPressedAnda lewati RaisedButtonbukanlah fungsi. Ubah ke() => _displaySnackBar(context)
Rémi Rousselet
Gotcha: onPressed: () {_displaySnackBar (konteks);}, Terima kasih =)
Figen Güngör
1
Saya pikir dengan .of (konteks) seharusnya naik hierarki widget sampai bertemu dengan salah satu tipe yang diberikan, dalam hal ini, Scaffold, yang harus ditemui sebelum mencapai "membangun Widget (..". Apakah tidak berfungsi
lewat sini
@ RémiRousselet, Berapa banyak jenis konteks yang ada di Flutter? Seperti yang Anda katakan konteks widget, konteks anak Scaffold.
CopsOnRoad
121

Anda bisa menggunakan a GlobalKey. Satu-satunya downside adalah bahwa menggunakan GlobalKey mungkin bukan cara yang paling efisien untuk melakukan ini.

Hal yang baik tentang ini adalah Anda juga dapat meneruskan kunci ini ke kelas widget khusus lainnya yang tidak mengandung perancah apa pun. Lihat (di sini )

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
sumber
Terima kasih Lebohang Mbele
Pengembang
1
Dapatkah saya bertanya bagaimana cara mereplikasi ini ketika pohon Anda terdiri dari beberapa widget yang ditentukan dalam banyak file? _scaffoldKeybersifat pribadi karena garis bawah utama. Tetapi bahkan jika tidak, itu ada di dalam HomePagekelas dan karena itu tidak tersedia tanpa membuat HomePage baru di suatu tempat di bawah pohon, tampaknya membuat referensi melingkar.
ExactaBox
1
untuk menjawab pertanyaan saya sendiri: buat kelas singleton dengan GlobalKey<ScaffoldState>properti, edit konstruktor Widget dengan perancah untuk mengatur properti kunci singleton, lalu di pohon widget anak kita dapat memanggil sesuatu seperti mySingleton.instance.key.currentState.showSnackBar()...
ExactaBox
Mengapa ini tidak efisien?
user2233706
@ user2233706 Ini disebutkan dalam dokumentasi GlobalKey di sini . Juga posting ini mungkin memberi lebih banyak cahaya.
Lebohang Mbele
43

Dua cara untuk mengatasi masalah ini

1) Menggunakan widget 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) Menggunakan 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);
          },
        ),
     );
   }
}
Sanjayrajsinh
sumber
16

Periksa ini dari dokumentasi metode:

Ketika Scaffold sebenarnya dibuat dalam fungsi build yang sama, argumen konteks ke fungsi build tidak dapat digunakan untuk menemukan Scaffold (karena itu "di atas" widget yang dikembalikan). Dalam kasus seperti itu, teknik berikut dengan Builder dapat digunakan untuk memberikan lingkup baru dengan BuildContext yang "di bawah" 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!'),
              ));
            },
          ),
        );
      },
    ),
  );
}

Anda dapat memeriksa deskripsi dari dokumentasi metode

SANAT
sumber
13

Cara sederhana untuk menyelesaikan masalah ini adalah membuat kunci untuk perancah Anda seperti tugas akhir ini dengan kode berikut:

Pertama: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Scecond: Tetapkan Kunci untuk Scaffold Anda key: _scaffoldKey

Ketiga: Panggil Snackbar menggunakan _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));

Mohamed Abdul-Malik
sumber
3

Perilaku yang Anda alami bahkan disebut sebagai "kasus rumit" dalam dokumentasi Flutter .

Bagaimana cara memperbaiki

Masalah ini diperbaiki dengan cara yang berbeda seperti yang Anda lihat dari jawaban lain yang diposting di sini. Sebagai contoh, bagian dari dokumentasi yang saya rujuk untuk menyelesaikan masalah dengan menggunakan Builderyang menciptakan

batin BuildContextsehingga onPressedmetode dapat merujuk ke Scaffolddengan Scaffold.of().

Jadi cara untuk menelepon showSnackBardari Scaffold adalah

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

Sekarang beberapa detail untuk pembaca yang ingin tahu

Saya sendiri menemukan cukup instruktif untuk mengeksplorasi dokumentasi Flutter hanya dengan ( Android Studio ) mengatur kursor pada sepotong kode ( kelas Flutter , metode, dll.) Dan menekan ctrl + B untuk ditampilkan dokumentasi untuk bagian tertentu.

Masalah khusus yang Anda hadapi disebutkan dalam dokumen untuk BuildContext , di mana dapat dibaca

Setiap widget memiliki BuildContext sendiri , yang menjadi induk widget yang dikembalikan oleh fungsi [...].

Jadi, ini berarti bahwa dalam konteks kasus kami akan menjadi induk widget Scaffold kami saat dibuat (!). Lebih lanjut, dokumen untuk Scaffold.of mengatakan bahwa ia kembali

Status dari instance [ Scaffold ] terdekat dari kelas ini yang menyertakan konteks yang diberikan.

Tetapi dalam kasus kami, konteks tidak menyertakan (belum) sebuah Scaffold (belum dibangun). Di situlah Builder bertindak!

Sekali lagi, dokumen itu menerangi kita. Di sana kita bisa membaca

[Kelas Builder, hanyalah] Sebuah widget platonis yang memanggil penutupan untuk mendapatkan widget turunannya.

Hei, tunggu sebentar, apa !? Ok, saya akui: itu tidak banyak membantu ... Tapi itu sudah cukup untuk mengatakan (mengikuti utas SO lainnya ) itu

Tujuan dari kelas Builder hanyalah untuk membangun dan mengembalikan widget anak.

Jadi sekarang semuanya menjadi jelas! Dengan memanggil Builder di dalam Scaffold, kami sedang membangun Scaffold agar dapat memperoleh konteksnya sendiri, dan dipersenjatai dengan innerContext tersebut, kami akhirnya dapat memanggil Scaffold.of (innerContext)

Versi kode yang dijelaskan di atas mengikuti

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

Saya tidak akan repot menggunakan snackbar default, karena Anda dapat mengimpor paket flushbar, yang memungkinkan penyesuaian lebih besar:

https://pub.dev/packages/flushbar

Sebagai contoh:

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

Solusi yang lebih efisien adalah dengan membagi fungsi build Anda menjadi beberapa widget. Ini memperkenalkan 'konteks baru', dari mana Anda dapat memperoleh 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'),
              ),
            );
          }),
    );
  }
}
TDM
sumber
0

Ekstrak widget tombol Anda yang akan menampilkan snackbar.

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'),
                  );
  }
}
Rashid
sumber
Bagaimana pendekatan Anda dibandingkan dengan jawaban yang ada? Apakah ada alasan untuk memilih pendekatan ini daripada, katakanlah, jawaban yang diterima?
Jeremy Caney
0

di sini kita menggunakan pembangun untuk membungkus widget lain di mana kita membutuhkan snackbar

Builder(builder: (context) => GestureDetector(
    onTap: () {
        Scaffold.of(context).showSnackBar(SnackBar(
            content: Text('Your Services have been successfully created Snackbar'),
        ));
        
    },
    child: Container(...)))
Krishna Chaitanya
sumber