[플러터] GlobalKey를 설정하는 방법

Posted on

GlobalKey를 설정하면 State를 설정한 클래스의 함수를 다른 위계 구조의 클래스에서 불러와서 사용할 수 있습니다. 최소구조로 소개된 소스코드 ( https://coens.tistory.com/5 ) 를 보면 아래와 용법이 같습니다.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyKey(),
    );
  }
}

class MyKey extends StatelessWidget {
  final counterKey = GlobalKey<_CounterState>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Counter(
          key: counterKey,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counterKey.currentState!.increment();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

class Counter extends StatefulWidget {
  const Counter({Key? key}) : super(key: key);

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return Text('$count');
  }

  void increment() {
    setState(() {
      count++;
    });
  }
}
  • 글로벌 키 생성final counterKey = GlobalKey<_CounterState>();
  • 해당키를 stateful위젯의 생성자에 넣음Counter(key: counterKey,)
  • 위젯의 state내부의 메서드를 사용counterKey.currentState!.increment();

주목해야 하는 지점은 MyKey 클래스에서 선언한 final counterKey = GlobalKey<_CounterState>(); 값을 Center() 위젯의 child: 속성으로 준 Counter() 생성자의 key: 속성에 인자로 주었다는 것입니다. 이렇게 StatefulWidget을 생성자로 호출하면 해당 State가 트리에 등록되어 위계 구조가 다른 클래스에서 해당 State에서 정의한 변수와 함수에 접근해서 사용하는게 가능해지는 구조입니다.

그런데 실수로 GlobalKey 값을 StatefulWidget 생성자에 안넘기고

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Counter(), 
        key: counterKey,  
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counterKey.currentState!.increment();
        },
        child: Icon(Icons.add),
      ),
    );

처럼 한다든지 하면, 오류가 납니다. 위 코드의 차이점은 key: 를 Counter()의 속성으로 넘긴게 아니라 Center() 위젯의 속성으로 넘긴 것입니다. 이렇게 하면 오류가 납니다.

그이유를 추적하려고 플러터 API의 https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/framework.dart 를 살펴보니 아래와 같은 GlobalKey 클래스가 유의되었습니다.

@optionalTypeArgs
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
  /// Creates a [LabeledGlobalKey], which is a [GlobalKey] with a label used for
  /// debugging.
  ///
  /// The label is purely for debugging and not used for comparing the identity
  /// of the key.
  factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);

  /// Creates a global key without a label.
  ///
  /// Used by subclasses because the factory constructor shadows the implicit
  /// constructor.
  const GlobalKey.constructor() : super.empty();

  Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];

  /// The build context in which the widget with this key builds.
  ///
  /// The current context is null if there is no widget in the tree that matches
  /// this global key.
  BuildContext? get currentContext => _currentElement;

  /// The widget in the tree that currently has this global key.
  ///
  /// The current widget is null if there is no widget in the tree that matches
  /// this global key.
  Widget? get currentWidget => _currentElement?.widget;

  /// The [State] for the widget in the tree that currently has this global key.
  ///
  /// The current state is null if (1) there is no widget in the tree that
  /// matches this global key, (2) that widget is not a [StatefulWidget], or the
  /// associated [State] object is not a subtype of T`.
  T? get currentState {
    final Element? element = _currentElement;
    if (element is StatefulElement) {
      final StatefulElement statefulElement = element;
      final State state = statefulElement.state;
      if (state is T) {
        return state;
      }
    }
    return null;
  }
}


중요한 부분은 마지막에 currentState를 가져오는 getter 부분인데요. 모종의 이유로 State 클래스에 global key가 지정이 안되게 되면 currentState 값이 null이 된다는 것 같습니다. (1)은 global key에 대응되는 위젯이 트리에 없는 경우이고 (2)는 createState를 실행하도록 된 StatefulWidget이 아닌 경우 (3) State 객체가 T의 서브타입이 아닌 경우로 특정되는데요. 그래서 위에 인용한 https://coens.tistory.com/5 에서 제시한 코드를 최소구조로 보고 잘 궁리해서 써야겠습니다.

이것은 주관적 추측인데요. (2)가 StatefulWidget 이어야 하는 이유는 createState()를 거쳐서 State가 정의되지 않으면 State에서 정의된 setState()가 불가능해서 그런게 아닌가 추측하는데 일단 이정도로 마칩니다. 위에 쓴 내용은 확인된 것인데 기술상으로 애매하거나 틀린 부분이 있을 수 있습니다. 최소 구조로 제시된 것을 지키면 일단 작동할 것입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다