크로스 플랫폼/Flutter <Dart>

[flutter] BuildContext 그리고 ScaffoldState.of() 에 대한 이해

TaeGyeong Lee 2024. 1. 6. 15:53

BuildContext

위젯 트리 내 위젯의 위치 정보를 가지는 클래스입니다. StatelessWidget.build 또는 State 객체(주로 StatefulWidget과 같이 활용되는)에서 사용가능한 메소드들을 제공합니다.

 

StatelessWidget.build

아래 코드와 같이 StatelessWidget.build 메소드를 사용였습니다. build 메소드 하위 블록 {} 에서 context 를 이용한 작업 수행이 가능합니다.

import 'package:flutter/material.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    ...
  }
}

 

난 context를 선언하지 않았는데? 

이 부분 추가로 글 작성하겠습니다. 

 

context.hashcode

이후 다룰 예제에서 두 개 이상의 BuildContext가 서로 다른 BuildContext인지 확인하기 위해 hashcode 속성을 사용하겠습니다. 

import 'package:flutter/material.dart';

void main() => runApp(const ExampleApp());

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    ...
    print(context.hashcode); // 예: 805658925
    ...
  }
}

 

of(context)

주로 Scaffold 에서 BottomSheet를 통제할 때 사용합니다. 이외, 다양하게 사용 가능합니다.

주의해야 할 점은, of 메소드는 인자로 받은 BuildContext의 가장 가까운 부모 BuildContext부터 탐색합니다. 아래는 ScaffoldState.of(BuildContext context) 메소드 정의 소스코드 일부입니다. findAncestorStateOfType 메소드를 통해 인자로 받은 context기준 부모 ScaffoldState를 반환하도록 설계되어 있습니다.

static ScaffoldState of(BuildContext context) {
    final ScaffoldState? result = context.findAncestorStateOfType<ScaffoldState>();
    if (result != null) {
      return result;
    }
    ...

 

따라서 아래 예제는 오류를 출력합니다. 예제의 of 메소드는 Scaffold 부모 위젯의 BuildContext부터 탐색합니다.

import 'package:flutter/material.dart';

void main() => runApp(const ShowBottomSheetExampleApp());

class ShowBottomSheetExampleApp extends StatelessWidget {
  const ShowBottomSheetExampleApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child : ElevatedButton(
        child: const Text('showBottomSheet'),
        onPressed: () {
          Scaffold.of(context).showBottomSheet<void>(
            (BuildContext context) {
              return Container(
                height: 200,
                color: Colors.amber,
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      const Text('BottomSheet'),
                      ElevatedButton(
                        child: const Text('Close BottomSheet'),
                        onPressed: () {
                          Navigator.pop(context);
                        },
                      ),
                    ],
                  ),
                ),
              );
            },
          );
        },)
            
        ),
      ),
    );
  }
}
══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════
The following assertion was thrown while handling a gesture:
Scaffold.of() called with a context that does not contain a Scaffold.
No Scaffold ancestor could be found starting from the context that was passed to Scaffold.of(). This
usually happens when the context provided is from the same StatefulWidget as that whose build
function actually creates the Scaffold widget being sought.

 

이 문제를 해결하기 위해 Scaffold 위젯 내 자식 Builder 위젯을 만들어 Builder 위젯의 BuilderContext를 전달하도록 만들어 주세요. of 메소드는 이제 Builder 위젯의 BuildContext의 부모인 Scaffold의 BuildContext부터 탐색합니다.

import 'package:flutter/material.dart';

void main() => runApp(const ShowBottomSheetExampleApp());

class ShowBottomSheetExampleApp extends StatelessWidget {
  const ShowBottomSheetExampleApp({super.key});
  
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child : Builder(
            builder : (BuildContext context) {
                return ElevatedButton(
        child: const Text('showBottomSheet'),
        onPressed: () {
          Scaffold.of(context).showBottomSheet<void>(
            (BuildContext context) {
              return Container(
                height: 200,
                color: Colors.amber,
                child: Center(
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    mainAxisSize: MainAxisSize.min,
                    children: <Widget>[
                      const Text('BottomSheet'),
                      ElevatedButton(
                        child: const Text('Close BottomSheet'),
                        onPressed: () {
                          Navigator.pop(context);
                        },
                      ),
                    ],
                  ),
                ),
              );
            },
          );
        },); 
            })
        ),
      ),
    );
  }
}

 

그럼 BuildContext들을 서로 구별할 수 있도록 context1 context2 로 바꾸어 출력결과를 보겠습니다. 서로 다른 hashCode를 출력하는 것을 확인할 수 있습니다. 아래 코드에서 Scaffold.of(context2) 를 Scaffold.of(context1)로 바꾸면 에러가 납니다.

import 'package:flutter/material.dart';

void main() => runApp(const ShowBottomSheetExampleApp());

class ShowBottomSheetExampleApp extends StatelessWidget {
  const ShowBottomSheetExampleApp({super.key});
  
  @override
  Widget build(BuildContext context1) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child : Builder(
            builder : (BuildContext context2) {
                return ElevatedButton(
        child: const Text('showBottomSheet'),
        onPressed: () {
        
          print(context1.hashCode); // 1064410590
          print(context2.hashCode); // 931118754
          
          Scaffold.of(context2).showBottomSheet<void>(
            (BuildContext context) {
              return Container(
                height: 200,
                color: Colors.amber,
                child: Center(
                  ...

 

참고 자료

 

BuildContext class - widgets library - Dart API

A handle to the location of a widget in the widget tree. This class presents a set of methods that can be used from StatelessWidget.build methods and from methods on State objects. BuildContext objects are passed to WidgetBuilder functions (such as Statele

api.flutter.dev

 

hashCode property - Object class - dart:core library - Dart API

 

api.flutter.dev

 

showBottomSheet method - ScaffoldState class - material library - Dart API

PersistentBottomSheetController showBottomSheet (WidgetBuilder builder, {Color? backgroundColor, double? elevation, ShapeBorder? shape, Clip? clipBehavior, BoxConstraints? constraints, bool? enableDrag, AnimationController? transitionAnimationController} )

api.flutter.dev