꿈꾸는 시스템 디자이너

Flutter 강좌2 - 스낵바(SnackBar)를 출력하는 다양한 방법 본문

Development/Flutter

Flutter 강좌2 - 스낵바(SnackBar)를 출력하는 다양한 방법

독행소년 2019. 8. 9. 04:13

Flutter 강좌 시즌2 목록 : https://here4you.tistory.com/149

 

 

지난 강좌에서 스낵바를 출력하는 방법에 대해서 살펴본적이 있다.

https://here4you.tistory.com/121

 

Flutter 강좌 - Display a snackbar | 스낵바 사용법

Flutter 강좌 목록 : https://here4you.tistory.com/120 이번 강좌에서는 스낵바의 사용법에 대해서 알아본다. 안드로이드에 토스트 메시지가 있다면 Flutter에는 스낵바 위젯이 있다. 스낵바의 모습은 다음과 같..

here4you.tistory.com

 

지난 강좌의 소스코드를 살펴보면, 딱히 어려운 부분이 없는 쉬운 소스코드였다. 그래서 편하게 스낵바를 사용하면 되는줄 알았는데 그게 생각처럼 쉽지 않았다.

 

의문의 실패

// Scaffold를 생성하는 build()에서 Scaffold.of(context)를 이용해 스낵바 출력 시도하는 경우 -> 에러 발생
class SnackBarTest1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SnackBarTest1')),
      body: Center(
        child: RaisedButton(
          child: Text("Show Snackbar"),
          onPressed: () {
            // 스낵바 출력 시도 - 에러 발생
            Scaffold.of(context)
                .showSnackBar(SnackBar(content: Text("SnackBarTest1")));
          },
        ),
      ),
    );
  }
}

간단하게 스낵바를 출력하기 위해 위와 같이 소스를 작성하고 실행을 해봤다. 그런데 다음과 같은 에러가 발생했다.

Performing hot restart...
Syncing files to device Android SDK built for x86...
Restarted application in 1,320ms.
I/flutter ( 3398): ══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
I/flutter ( 3398): The following assertion was thrown while handling a gesture:
I/flutter ( 3398): Scaffold.of() called with a context that does not contain a Scaffold.
I/flutter ( 3398): No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This
I/flutter ( 3398): usually happens when the context provided is from the same StatefulWidget as that whose build
I/flutter ( 3398): function actually creates the Scaffold widget being sought.
I/flutter ( 3398): There are several ways to avoid this problem. The simplest is to use a Builder to get a context that
I/flutter ( 3398): is "under" the Scaffold. For an example of this, please see the documentation for Scaffold.of():
I/flutter ( 3398):   https://api.flutter.dev/flutter/material/Scaffold/of.html
I/flutter ( 3398): A more efficient solution is to split your build function into several widgets. This introduces a
I/flutter ( 3398): new context from which you can obtain the Scaffold. In this solution, you would have an outer widget
I/flutter ( 3398): that creates the Scaffold populated by instances of your new inner widgets, and then in these inner
I/flutter ( 3398): widgets you would use Scaffold.of().
I/flutter ( 3398): A less elegant but more expedient solution is assign a GlobalKey to the Scaffold, then use the
I/flutter ( 3398): key.currentState property to obtain the ScaffoldState rather than using the Scaffold.of() function.
I/flutter ( 3398): The context used was:
I/flutter ( 3398):   SnackBarTest1
I/flutter ( 3398): 
I/flutter ( 3398): When the exception was thrown, this was the stack:
I/flutter ( 3398): #0      Scaffold.of (package:flutter/src/material/scaffold.dart:1188:5)
I/flutter ( 3398): #1      SnackBarTest1.build.<anonymous closure> (package:snackbar_test/main.dart:23:22)
I/flutter ( 3398): #2      _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:635:14)
I/flutter ( 3398): #3      _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:711:32)
I/flutter ( 3398): #4      GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
I/flutter ( 3398): #5      TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:365:11)
I/flutter ( 3398): #6      TapGestureRecognizer.handlePrimaryPointer (package:flutter/src/gestures/tap.dart:275:7)
I/flutter ( 3398): #7      PrimaryPointerGestureRecognizer.handleEvent (package:flutter/src/gestures/recognizer.dart:455:9)
I/flutter ( 3398): #8      PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:75:13)
I/flutter ( 3398): #9      PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:102:11)
I/flutter ( 3398): #10     _WidgetsFlutterBinding&BindingBase&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:218:19)
I/flutter ( 3398): #11     _WidgetsFlutterBinding&BindingBase&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:198:22)
I/flutter ( 3398): #12     _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerEvent (package:flutter/src/gestures/binding.dart:156:7)
I/flutter ( 3398): #13     _WidgetsFlutterBinding&BindingBase&GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:102:7)
I/flutter ( 3398): #14     _WidgetsFlutterBinding&BindingBase&GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:86:7)
I/flutter ( 3398): #18     _invoke1 (dart:ui/hooks.dart:250:10)
I/flutter ( 3398): #19     _dispatchPointerDataPacket (dart:ui/hooks.dart:159:5)
I/flutter ( 3398): (elided 3 frames from package dart:async)
I/flutter ( 3398): 
I/flutter ( 3398): Handler: "onTap"
I/flutter ( 3398): Recognizer:
I/flutter ( 3398):   TapGestureRecognizer#68473
I/flutter ( 3398): ════════════════════════════════════════════════════════════════════════════════════════════════════

Scaffold.of(context)를 이용하면 해당 context의 Scaffold에 접근하여 스낵바를 출력할 수 있는데, 위 소스에서는 Scaffold의 Scaffold를 참조하는 형태이기 때문에 에러가 발생하는 것 같다.

 

첫번째 해결법

// Scaffold를 생성하는 build()에서 body를 구성하는 별도의 클래스를 호출하는 경우
class SnackBarTest2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SnackBarTest2')),
      body: BodyOfSnackBarTest2(),
    );
  }
}

// Scaffold의 하위 위젯인 body를 구성하는 클래스 -> 정상 출력됨
class BodyOfSnackBarTest2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
        child: Text("Show Snackbar"),
        onPressed: () {
          // 스낵바 출력 시도 - 정상 출력
          Scaffold.of(context)
              .showSnackBar(SnackBar(content: Text("SnackBarTest2")));
        },
      ),
    );
  }
}

Scaffold의 body를 구성하는 별도의 클래스내부에서 Scaffold.of(context)를 이용하면 스낵바를 출력할 수 있다. 위 소스에서는 Center를 반환하는 build() 내부에서 Scaffold.of(context)를 이용하기 때문에, Center 위젯를 가지는 Scaffold를 참조하여 스낵바가 출력되는 것 같다.

 

두번째 방법

// Scaffold를 생성하는 build()안에서 body를 구성하는 build()를 한번 더 구현
class SnackBarTest3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SnackBarTest3')),
      // body에 빌더를 주입
      body: Builder(builder: (BuildContext context) {
        return Center(
          child: RaisedButton(
            child: Text("Show Snackbar"),
            onPressed: () {
              // 스낵바 출력 시도 - 정상 출력
              Scaffold.of(context)
                  .showSnackBar(SnackBar(content: Text("SnackBarTest3")));
            },
          ),
        );
      }),
    );
  }
}

이 방번은 Scaffold를 반환하는 build() 내부에서 다시한번 Builder()를 생성하여 Builder() 내부에서 Scaffold.of(context)를 이용하여 스낵바를 출력하고 있다. 즉 Scaffold.of(context)에서의 context는 Center를 의미하며 이 Center 위젯을 가지는 Scaffold를 참조하여 스낵바를 출력한다고 이해된다.

위 두가지 방법을 이용하면 스낵바가 정상 출력되는데 Flutter의 프레임워의 구동 방식이 쉽게 이해가 가지 않는다.

 

세번째 방법

// Scaffold의 key값을 이용하여 참조하는 방법
class SnackBarTest4 extends StatelessWidget {
  // Scaffold용 글로벌 키를 발급 받음
  final scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey, // 발급한 키를 Scaffold에 등록
      appBar: AppBar(title: Text('SnackBarTest4')),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("Show Snackbar"),
              onPressed: () {
                // 스낵바 출력 시도 - 에러 발생
//            Scaffold.of(context)
//                .showSnackBar(SnackBar(content: Text("SnackBarTest4")));

                // 키를 통해 Scaffold를 참조하여 스낵바 출력
                scaffoldKey.currentState
                    .showSnackBar(SnackBar(content: Text("SnackBarTest4 - 1")));
              },
            ),
            RaisedButton(
              child: Text("Show Snackbar2"),
              onPressed: () {
                showSnackbarWithKey();
              },
            ),
            RaisedButton(
              child: Text("Show Snackbar3"),
              onPressed: () {
                SnackbarManager.showSnackBar(scaffoldKey, "Hello");
              },
            ),
          ],
        ),
      ),
    );
  }

  showSnackbarWithKey() {
    // 키를 통해 Scaffold에 접근하여 스낵바 출력
    scaffoldKey.currentState
        .showSnackBar(SnackBar(content: Text("SnackBarTest4 - 2")));
  }
}

class SnackbarManager {
  static void showSnackBar(
      GlobalKey<ScaffoldState> scaffoldKey, String message) {
    scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message)));
  }
}

이 방법이 제일 확실한 방법이다. Scaffold.of(context)를 이용해서 현재 위젯을 가지는 Scaffold를 참조하여 스낵바를 출력하는 첫번째와 두번째 방법과 달리 Scaffold를 참조하는 Key를 이용해서 스낵를 출력하는 것이다.

우선 GlobalKey<Scaffoldstate>()를 통해 Scaffold에 주입할 key값을 발급한다(line 4)

그리고 그 key 값을 Scaffold의 key 속성에 주입한다(line 9).

마지막으로, 스낵바를 호출할 Scaffold에 접근하고자 할 때에는 scaffoldKey.currentState를 이용해서 스낵바를 출력한다.

key 값을 통해서 Scaffold에 접근하는 방식을 이용하기 때문에 Scaffold를 구현하는 build() 내부에서도 접근 가능하고 별도의 외부 메소드나 외부 클래스에서도 스낵바를 출력할 수 있게된다.

 

전체 소스코드는 다음과 같다.

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: SnackBarTest1(),
//    home: SnackBarTest2(),
//    home: SnackBarTest3(),
//    home: SnackBarTest4(),
  ));
}

// Scaffold를 생성하는 build()에서 Scaffold.of(context)를 이용해 스낵바 출력 시도하는 경우 -> 에러 발생
class SnackBarTest1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SnackBarTest1')),
      body: Center(
        child: RaisedButton(
          child: Text("Show Snackbar"),
          onPressed: () {
            // 스낵바 출력 시도 - 에러 발생
            Scaffold.of(context)
                .showSnackBar(SnackBar(content: Text("SnackBarTest1")));
          },
        ),
      ),
    );
  }
}

// Scaffold를 생성하는 build()에서 body를 구성하는 별도의 클래스를 호출하는 경우
class SnackBarTest2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SnackBarTest2')),
      body: BodyOfSnackBarTest2(),
    );
  }
}

// Scaffold의 하위 위젯인 body를 구성하는 클래스 -> 정상 출력됨
class BodyOfSnackBarTest2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
        child: Text("Show Snackbar"),
        onPressed: () {
          // 스낵바 출력 시도 - 정상 출력
          Scaffold.of(context)
              .showSnackBar(SnackBar(content: Text("SnackBarTest2")));
        },
      ),
    );
  }
}

// Scaffold를 생성하는 build()안에서 body를 구성하는 build()를 한번 더 구현
class SnackBarTest3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('SnackBarTest3')),
      // body에 빌더를 주입
      body: Builder(builder: (BuildContext context) {
        return Center(
          child: RaisedButton(
            child: Text("Show Snackbar"),
            onPressed: () {
              // 스낵바 출력 시도 - 정상 출력
              Scaffold.of(context)
                  .showSnackBar(SnackBar(content: Text("SnackBarTest3")));
            },
          ),
        );
      }),
    );
  }
}

// Scaffold의 key값을 이용하여 참조하는 방법
class SnackBarTest4 extends StatelessWidget {
  // Scaffold용 글로벌 키를 발급 받음
  final scaffoldKey = GlobalKey<ScaffoldState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey, // 발급한 키를 Scaffold에 등록
      appBar: AppBar(title: Text('SnackBarTest4')),
      body: Center(
        child: Column(
          children: <Widget>[
            RaisedButton(
              child: Text("Show Snackbar"),
              onPressed: () {
                // 스낵바 출력 시도 - 에러 발생
//            Scaffold.of(context)
//                .showSnackBar(SnackBar(content: Text("SnackBarTest4")));

                // 키를 통해 Scaffold를 참조하여 스낵바 출력
                scaffoldKey.currentState
                    .showSnackBar(SnackBar(content: Text("SnackBarTest4 - 1")));
              },
            ),
            RaisedButton(
              child: Text("Show Snackbar2"),
              onPressed: () {
                showSnackbarWithKey();
              },
            ),
            RaisedButton(
              child: Text("Show Snackbar3"),
              onPressed: () {
                SnackbarManager.showSnackBar(scaffoldKey, "Hello");
              },
            ),
          ],
        ),
      ),
    );
  }

  showSnackbarWithKey() {
    // 키를 통해 Scaffold에 접근하여 스낵바 출력
    scaffoldKey.currentState
        .showSnackBar(SnackBar(content: Text("SnackBarTest4 - 2")));
  }
}

class SnackbarManager {
  static void showSnackBar(
      GlobalKey<ScaffoldState> scaffoldKey, String message) {
    scaffoldKey.currentState.showSnackBar(SnackBar(content: Text(message)));
  }
}

 


Flutter Code Examples 강좌를 추천합니다.

  • 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.
  • Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.
  • 또한 모든 예제는 Flutter Code Examples 앱을 통해 테스트 가능합니다.

Flutter Code Examples 강좌로 메뉴로 이동

Flutter Code Examples 강좌 목록 페이지로 이동

Flutter Code Examples 앱 설치 | Google Play Store로 이동

 

Flutter Code Examples - Google Play 앱

Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.

play.google.com

Comments