스캐 폴드를 포함하지 않는 컨텍스트로 호출 된 Scaffold.of ()


183

보시다시피 내 버튼은 비계의 몸 안에 있습니다. 그러나이 예외가 발생합니다.

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 인 키를 주면, 우리 몸을 Builder 위젯으로 감싸지 않고도 다음과 같이 SnackBar를 표시 할 수 있습니다. 스캐 폴드를 반환하는 위젯은 Stateful 위젯이어야합니다. :

 _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팩터 사용 ).


이 예외는 다음과 같습니다. setState () 또는 markNeedsBuild ()가 빌드 중에 호출되었습니다. I / 플러터 (21754) :이 스캐 폴드 위젯은 프레임 워크가 이미 I / 플러터 (21754) : 위젯 빌드 프로세스에 있으므로 빌드해야하는 것으로 표시 할 수 없습니다.
Figen Güngör

1
onPressed당신이 전달이 RaisedButton기능하지 않습니다. 다음으로 변경() => _displaySnackBar(context)
Rémi Rousselet

Gotcha : onPressed : () {_displaySnackBar (context);}, Thanks =)
Figen Güngör

1
.of (context)를 사용하면 주어진 유형 중 하나 (이 경우 Scaffold)에 도달 할 때까지 위젯 계층 구조를 따라 올라 가야한다고 생각했는데,이 비계는 "Widget build (.." "에 도달하기 전에 발생합니다.) 이 방법은?
CodeGrue

@ RémiRousselet, Flutter에는 몇 가지 유형의 컨텍스트가 있습니까? 당신이 위젯의 ​​컨텍스트, 비계의 자식의 컨텍스트를 말했듯이.
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클래스 내부에 있으므로 트리의 어딘가에 새 HomePage를 인스턴스화하지 않고 사용할 수 없으므로 순환 참조를 만듭니다.
ExactaBox

1
내 자신의 질문에 대답하려면 : GlobalKey<ScaffoldState>속성을 사용하여 싱글 톤 클래스를 만들고 , 스캐 폴드로 위젯 생성자를 편집하여 싱글 톤의 키 속성을 설정 한 다음 자식 위젯의 트리에서 다음과 같이 호출 할 수 있습니다 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

메소드 문서에서이를 확인하십시오.

스캐 폴드가 실제로 동일한 빌드 함수로 작성되면 빌드 기능에 대한 컨텍스트 인수를 사용하여 스캐 폴드를 찾을 수 없습니다 (위젯이 리턴되는 위의 "위"이므로). 이러한 경우 빌더가있는 다음 기술을 사용하여 스캐 폴드 아래에있는 BuildContext에 새 범위를 제공 할 수 있습니다.

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

에서 설명을 확인할 수 있습니다 docs 메소드


13

이 문제를 해결하는 간단한 방법은 다음 코드를 사용하여이 결승과 같은 비계의 열쇠를 만드는 것입니다.

먼저: GlobalKey<ScaffoldState>() _scaffoldKey = GlobalKey<ScaffoldState> ();

Scecond : 비계에 키 할당 key: _scaffoldKey

셋째 : 다음을 사용하여 스낵바를 호출하십시오. _scaffoldKey.currentState.showSnackBar(SnackBar(content: Text("Welcome")));


3

당신이 겪고있는 바로 그 행동은 Flutter 문서 에서 "까다로운 사건"이라고도합니다 .

어떻게 고치는 지

여기에 게시 된 다른 답변에서 볼 수 있듯이 문제는 다른 방식으로 수정되었습니다. 예를 들어, 문서의 일부는 전 사용으로 해결할 수있는 문제에 회부 Builder생성 어느

내측 BuildContext되도록 onPressed방법을 참조 할 수 ScaffoldScaffold.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.')
            ));
          }
        );
      }
    )
  );
}

이제 호기심 많은 독자들을위한 세부 사항

나 자신 이 간단하게 ( Android Studio ) 커서를 코드 조각 ( Flutter )에 설정하여 Flutter 설명서 를 탐색하는 것이 유익하다는 것을 알았습니다. 클래스, 메소드 등) 놓고 Ctrl + B를 눌러 해당 특정 부분에 대한 문서를 표시 .

당신이 직면하고있는 특정 문제는 BuildContext 문서에서 언급 할 수 있습니다.

각 위젯에는 고유 한 BuildContext 가 있으며 [...]. build 함수가 반환 한 위젯의 부모가됩니다.

따라서이 경우 컨텍스트 가 생성 될컨텍스트 스캐 폴드 위젯의 상위가 됩니다 (!). 또한 Scaffold.of 의 문서 는 그것이 반환한다고 말합니다.

주어진 문맥을 둘러싸고있는이 클래스 의 가장 가까운 [ Scaffold ] 인스턴스의 상태 .

그러나 우리의 경우 문맥 은 아직 아직 건설되지 않은 비계를 둘러싸 지 않습니다. 빌더 가 작동하는 곳이 있습니다 !

다시 한 번, 문서가 우리를 비 춥니 다. 우리는 읽을 수 있습니다

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

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

귀하의 접근 방식은 기존 답변과 어떻게 비교됩니까? 예를 들어 허용되는 답변보다이 방법을 선택해야하는 이유가 있습니까?
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.