티스토리 뷰

반응형
import 'dart:developer' as developer;

void main() {
  developer.log('log me', name: 'my.app.category');

  developer.log('log me 1', name: 'my.other.category');
  developer.log('log me 2', name: 'my.other.category');
}

이 가이드는 코드에서 활성화할 수 있는 디버깅 기능들을 설명합니다. 전체 디버깅 및 프로파일링 도구 목록은 [디버깅 페이지(Debugging page)]를 참고하세요.


 

애플리케이션에 로깅 추가하기

다음은 애플리케이션의 동작을 기록하는 데 사용할 수 있는 몇 가지 명령어입니다. DevTools의 Logging 뷰 또는 시스템 콘솔에서 로그를 확인할 수 있습니다.

  • print(): stdout(표준 출력) 메시지를 출력합니다. dart:io 라이브러리의 일부입니다.
  • stderr.메서드명(): stderr(표준 오류) 메시지를 출력합니다. 예: writeln() 또는 write() 같은 stderr가 지원하는 메서드로 대체하여 사용하세요. 일반적으로 try...catch 블록 내에서 사용됩니다. dart:io 라이브러리의 일부입니다.
stderr.writeln('print me');

 

  • log(): 로그 출력에 더 정밀한 제어 및 정보를 포함합니다. dart:developer 라이브러리의 일부입니다.
  • debugPrint(): 출력량이 너무 많아 로그 라인이 누락되는 경우 이를 방지할 수 있습니다. 릴리즈 모드에서도 출력되지만, 디버그 모드 체크나 assert와 함께 사용해야 합니다. foundation 라이브러리의 일부입니다.

 

예제 1

import 'dart:developer' as developer;

void main() {
  developer.log('log me', name: 'my.app.category');

  developer.log('log me 1', name: 'my.other.category');
  developer.log('log me 2', name: 'my.other.category');
}


앱 데이터를 log() 호출에 전달할 수도 있습니다. 일반적인 방식은 error: 명명 매개변수를 사용하는 것이며, 전달할 객체를 JSON 인코딩한 뒤 문자열로 전달하는 것입니다.

 

예제 2

import 'dart:convert';
import 'dart:developer' as developer;

void main() {
  var myCustomObject = MyCustomObject();

  developer.log(
    'log me',
    name: 'my.app.category',
    error: jsonEncode(myCustomObject),
  );
}

DevTools의 Logging 뷰는 error 매개변수에 전달된 JSON 문자열을 데이터 객체로 해석합니다. 해당 로그 항목의 Details 뷰에서 렌더링됩니다.


 

브레이크포인트 설정

브레이크포인트는 DevTools의 Debugger나 IDE의 내장 디버거에서 설정할 수 있습니다.

 

코드에서 브레이크포인트를 설정하려면:

  • 해당 파일에 dart:developer 패키지를 임포트합니다.
  • debugger() 문을 삽입하여 프로그래매틱 브레이크포인트를 설정합니다. 이 문은 선택적 when 인자를 받을 수 있으며, 이 불리언 인자가 true일 때 브레이크가 걸립니다.

 

예제 3

import 'dart:developer';

void someFunction(double offset) {
  debugger(when: offset > 30);
  // ...
}

 

앱 계층 디버깅을 플래그로 제어하기

Flutter 프레임워크의 각 계층은 debugPrint 속성을 사용해 현재 상태나 이벤트를 콘솔에 출력하는 함수를 제공합니다.

 

💡 참고 : 아래 예제들은 모두 MacBook Pro M1에서 macOS 네이티브 앱으로 실행한 결과입니다. 여러분의 개발 머신에서 출력되는 내용은 다를 수 있습니다.

 

💡 팁 : 트리 내 모든 렌더 객체는 해시 코드의 앞 5자리 16진수를 포함합니다. 이 해시는 해당 렌더 객체의 고유 식별자로 작용합니다.

 

위젯 트리 출력하기

Widgets 라이브러리의 상태를 출력하려면 debugDumpApp() 함수를 호출합니다.

1. 소스 파일을 엽니다.

2. package:flutter/rendering.dart를 import 합니다.

3. runApp() 함수 내부에서 debugDumpApp()을 호출합니다. 이 함수는 반드시 디버그 모드에서 호출되어야 하며, 앱이 빌드 중인 build() 메서드 안에서는 호출할 수 없습니다.

4. 앱을 아직 실행하지 않았다면, IDE에서 디버깅 모드로 실행합니다.

 

이미 실행된 경우, 소스 파일을 저장하여 Hot reload로 앱을 다시 렌더링합니다.

 

예제 4: debugDumpApp() 호출하기

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: AppHome()));
}

class AppHome extends StatelessWidget {
  const AppHome({super.key});

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: TextButton(
          onPressed: () {
            debugDumpApp();
          },
          child: const Text('Dump Widget Tree'),
        ),
      ),
    );
  }
}

 

이 함수는 루트 위젯부터 시작해 toStringDeep() 메서드를 재귀적으로 호출하여 "펼쳐진" 트리를 출력합니다.

 

예제 4의 출력 결과에는 다음이 포함됩니다:

  • 각 build 함수에서 생성된 모든 위젯
  • 소스 코드에는 없지만 프레임워크의 build 함수에서 삽입한 위젯들 (예: _InkFeatures 등)

예를 들어, 다음 트리는 _InkFeatures를 보여준다. 이 클래스는 Material 위젯의 일부를 구현하지만, Example 4의 코드 어디에도 나타나지 않는다.

 

버튼이 눌렸다가 떼지는 시점에 debugDumpApp() 함수가 호출된다. 이 시점은 TextButton 객체가 setState()를 호출하고 자신을 "dirty" 상태로 표시하는 시점과도 일치한다. 이것은 Flutter가 특정 객체를 왜 "dirty"로 표시하는지를 설명해준다. 위젯 트리를 검토할 때 다음과 유사한 줄을 찾아보라:

└TextButton(dirty, dependencies: [MediaQuery, _InheritedTheme, _LocalizationsScope-[GlobalKey#5880d]], state: _ButtonStyleState#ab76e)

자신만의 위젯을 작성한다면, debugFillProperties() 메서드를 오버라이드하여 정보를 추가하라. DiagnosticsProperty 객체들을 메서드의 인수에 추가하고, 슈퍼클래스 메서드를 호출하라. toString 메서드는 이 함수를 사용하여 위젯의 설명을 채운다.

 

렌더 트리 출력하기

레이아웃 문제를 디버깅할 때는 위젯 트리만으로는 부족할 수 있습니다. 이 경우 렌더 트리를 확인해야 할 수도 있습니다. 렌더 트리를 출력하려면:

소스 파일을 엽니다.

debugDumpRenderTree() 함수를 호출합니다. 레이아웃 또는 페인트 단계 중에는 호출할 수 없으며, 프레임 콜백이나 이벤트 핸들러에서 호출하는 것이 좋습니다.

앱을 아직 실행하지 않았다면 IDE에서 디버깅으로 실행합니다.

실행 중이라면 소스 파일을 저장하여 Hot reload합니다.

 

예제 5: debugDumpRenderTree() 호출하기

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: AppHome()));
}

class AppHome extends StatelessWidget {
  const AppHome({super.key});

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: TextButton(
          onPressed: () {
            debugDumpRenderTree();
          },
          child: const Text('Dump Render Tree'),
        ),
      ),
    );
  }
}


레이아웃 문제를 디버깅할 때는 size 및 constraints 필드를 주의 깊게 살펴보세요. constraints는 트리 아래로 흐르고, size는 위로 전달됩니다.

 

Example 5의 렌더 트리에서:

  • RenderView, 즉 윈도우 크기는 RenderPositionedBox#dc1df 렌더 객체를 포함한 모든 렌더 객체를 화면 크기로 제한한다. 이 예제에서는 크기가 Size(800.0, 600.0)으로 설정되어 있다.
  • 각 렌더 객체의 constraints 속성은 각 자식의 크기를 제한한다. 이 속성은 BoxConstraints 렌더 객체를 값으로 사용한다. RenderSemanticsAnnotations#fe6b5부터 시작하여, 제약 조건은 BoxConstraints(w=800.0, h=600.0)이다.
  • Center 위젯은 RenderSemanticsAnnotations#8187b 하위 트리 아래에 RenderPositionedBox#dc1df 렌더 객체를 생성했다.
  • 이 렌더 객체의 모든 자식은 최소 및 최대 값이 모두 지정된 BoxConstraints를 가진다. 예를 들어, RenderSemanticsAnnotations#a0a4b는 BoxConstraints(0.0<=w<=800.0, 0.0<=h<=600.0)를 사용한다.
  • RenderPhysicalShape#8e171 렌더 객체의 모든 자식은 BoxConstraints(BoxConstraints(56.0<=w<=800.0, 28.0<=h<=600.0))를 사용한다.
  • 자식 객체인 RenderPadding#8455f는 EdgeInsets(8.0, 0.0, 8.0, 0.0)의 패딩 값을 설정한다. 이는 이 렌더 객체의 이후 모든 자식들에게 좌우 패딩 8을 설정한다. 이제 이들은 새로운 제약 조건을 갖게 된다: BoxConstraints(40.0<=w<=784.0, 28.0<=h<=600.0).

creator 필드를 보면, 이 객체는 아마도 TextButton 정의의 일부로 보인다. 이 객체는 콘텐츠에 대해 최소 너비 88픽셀과 높이 36.0을 설정한다. 이것은 버튼의 치수에 대한 머티리얼 디자인 가이드라인을 구현하는 TextButton 클래스이다.

 

RenderPositionedBox#80b8d 렌더 객체는 텍스트를 버튼 안에서 중앙에 배치하기 위해 제약을 다시 완화시킨다. RenderParagraph#59bc2 렌더 객체는 자신의 콘텐츠에 기반해 크기를 결정한다. 트리 상단으로 사이즈를 따라 올라가다 보면 텍스트의 크기가 버튼을 구성하는 모든 박스들의 너비에 어떻게 영향을 미치는지 알 수 있다. 모든 부모는 자식의 치수를 참고하여 스스로의 크기를 결정한다.

 

이것을 알아내는 또 다른 방법은 각 박스의 설명에 있는 relayoutBoundary 속성을 보는 것이다. 이 속성은 이 요소의 크기에 의존하는 조상 수를 알려준다.

예를 들어, 가장 안쪽의 RenderPositionedBox 줄은 relayoutBoundary=up13을 가지고 있다. 이는 Flutter가 RenderConstrainedBox를 더럽다고 표시할 때, 그 박스의 13개의 조상들도 함께 더럽다고 표시한다는 뜻이다. 새로운 크기가 이 조상들에게 영향을 줄 수 있기 때문이다.

자신만의 렌더 객체를 작성한다면 덤프에 정보를 추가하려면 debugFillProperties()를 오버라이드하라. 메서드 인수에 DiagnosticsProperty 객체들을 추가하고 슈퍼클래스 메서드를 호출하라.

 

레이어 트리 출력

합성(compositing) 문제를 디버깅하려면 debugDumpLayerTree()를 사용하세요.

 

예제 6: debugDumpLayerTree() 호출

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: AppHome()));
}

class AppHome extends StatelessWidget {
  const AppHome({super.key});

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: TextButton(
          onPressed: () {
            debugDumpLayerTree();
          },
          child: const Text('Dump Layer Tree'),
        ),
      ),
    );
  }
}


RepaintBoundary 위젯은 다음을 생성합니다:

1. 렌더 트리에서는 RenderRepaintBoundary RenderObject를 생성합니다. 이는 예제 5 결과에 표시됩니다.

╎     └─child: RenderRepaintBoundary#f8f28
╎       │ needs compositing
╎       │ creator: RepaintBoundary ← _FocusInheritedScope ← Semantics ←
╎       │   FocusScope ← PrimaryScrollController ← _ActionsScope ← Actions
╎       │   ← Builder ← PageStorage ← Offstage ← _ModalScopeStatus ←
╎       │   UnmanagedRestorationScope ← ⋯
╎       │ parentData: <none> (can use size)
╎       │ constraints: BoxConstraints(w=800.0, h=600.0)
╎       │ layer: OffsetLayer#e73b7
╎       │ size: Size(800.0, 600.0)
╎       │ metrics: 66.7% useful (1 bad vs 2 good)
╎       │ diagnosis: insufficient data to draw conclusion (less than five
╎       │   repaints)


2. 레이어 트리에서는 새로운 레이어가 생성됩니다. 예제 6 결과에 나타납니다.

├─child 1: OffsetLayer#0f766
│ │ creator: RepaintBoundary ← _FocusInheritedScope ← Semantics ←
│ │   FocusScope ← PrimaryScrollController ← _ActionsScope ← Actions
│ │   ← Builder ← PageStorage ← Offstage ← _ModalScopeStatus ←
│ │   UnmanagedRestorationScope ← ⋯
│ │ engine layer: OffsetEngineLayer#1768d
│ │ handles: 2
│ │ offset: Offset(0.0, 0.0)

 

 

이는 다시 그려야 할 영역을 줄여줍니다.

 

포커스 트리 출력

포커스 또는 단축키 문제를 디버깅하려면 debugDumpFocusTree() 함수를 사용하여 포커스 트리를 출력하세요.

debugDumpFocusTree() 메서드는 앱의 포커스 트리를 반환합니다.

 

포커스 트리는 다음과 같은 방식으로 노드를 라벨링합니다:

  • 포커스된 노드는 PRIMARY FOCUS로 라벨링됩니다.
  • 포커스 노드의 조상들은 IN FOCUS PATH로 라벨링됩니다.

앱이 Focus 위젯을 사용하는 경우 debugLabel 속성을 사용하여 포커스 노드를 쉽게 찾을 수 있습니다.

 

또한 debugFocusChanges 불리언 속성을 사용하면 포커스 변경 시 자세한 로그를 활성화할 수 있습니다.

 

예제 7: debugDumpFocusTree() 호출

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(home: AppHome()));
}

class AppHome extends StatelessWidget {
  const AppHome({super.key});

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: TextButton(
          onPressed: () {
            debugDumpFocusTree();
          },
          child: const Text('Dump Focus Tree'),
        ),
      ),
    );
  }
}

 

시맨틱 트리 출력

debugDumpSemanticsTree() 함수는 앱의 시맨틱 트리를 출력합니다.

시맨틱 트리는 시스템의 접근성 API에 제공됩니다. 시맨틱 트리 덤프를 얻으려면:

1. 시스템 접근성 도구나 SemanticsDebugger를 사용하여 접근성을 활성화합니다.

2. debugDumpSemanticsTree() 함수를 사용합니다.

 

예제 8: debugDumpSemanticsTree() 호출

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(const MaterialApp(home: AppHome()));
}

class AppHome extends StatelessWidget {
  const AppHome({super.key});

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: Semantics(
          button: true,
          enabled: true,
          label: 'Clickable text here!',
          child: GestureDetector(
            onTap: () {
              debugDumpSemanticsTree();
              if (kDebugMode) {
                print('Clicked!');
              }
            },
            child: const Text('Click Me!', style: TextStyle(fontSize: 56)),
          ),
        ),
      ),
    );
  }
}

현재 프레임이 스케줄된 원인이 되는 호출 스택(call stack)을 출력하려면 debugPrintScheduleFrameStacks 플래그를 사용하세요.


 

레이아웃 문제 디버깅

GUI를 사용해 레이아웃 문제를 디버깅하려면, debugPaintSizeEnabled를 true로 설정하세요.

이 플래그는 렌더링 라이브러리에 있으며 언제든지 활성화할 수 있고, true로 설정되는 동안 모든 페인팅에 영향을 줍니다. void main()

 

입점 상단에 추가하는 것을 고려하세요.

 

예제 9

다음 코드에서 예제를 확인하세요:

// Flutter 렌더링 라이브러리 import 추가
import 'package:flutter/rendering.dart';

void main() {
  debugPaintSizeEnabled = true;
  runApp(const MyApp());
}

 

활성화되면, Flutter는 앱에 다음과 같은 변경 사항을 표시합니다:

  • 모든 박스를 밝은 청록색 테두리로 표시합니다.
  • 모든 패딩을 옅은 파란색 채우기와 파란색 테두리로 표시된 박스로 표시합니다.
  • 모든 정렬 위치를 노란색 화살표로 표시합니다.
  • 자식이 없는 Spacer는 회색으로 표시됩니다.

 

debugPaintBaselinesEnabled 플래그는 유사한 동작을 하지만 기준선이 있는 객체에 대해 동작합니다. 앱은 알파벳 문자 기준선을 밝은 녹색으로, 표의문자(CJK) 기준선을 주황색으로 표시합니다. 알파벳 문자는 알파벳 기준선에 "앉아" 있지만, 이 기준선은 CJK 문자 하단을 "가로지릅니다". Flutter는 텍스트 라인의 맨 아래에 표의문자 기준선을 위치시킵니다.

 

debugPaintPointersEnabled 플래그는 탭한 객체를 청록색으로 강조 표시하는 특수 모드를 켭니다. 이는 객체가 히트 테스트에 실패하는 경우에 객체를 식별하는 데 도움이 됩니다. 이 문제는 객체가 부모의 경계를 벗어나 히트 테스트 대상에서 제외될 때 발생할 수 있습니다.

 

합성기 레이어를 디버깅하려는 경우, 다음 플래그를 고려해보세요:

  • debugPaintLayerBordersEnabled 플래그는 각 레이어의 경계를 찾는 데 사용됩니다. 이 플래그는 각 레이어 경계에 주황색 상자를 그립니다.
  • debugRepaintRainbowEnabled 플래그는 다시 페인팅되는 레이어를 표시합니다. 레이어가 다시 페인팅될 때마다 회전하는 일련의 색상으로 오버레이됩니다.

 

debug...로 시작하는 Flutter 프레임워크의 모든 함수 또는 메서드는 디버그 모드에서만 작동합니다.


 

애니메이션 문제 디버깅

참고 : 애니메이션을 가장 쉽게 디버깅하려면 애니메이션 속도를 늦추는 것이 좋습니다.
애니메이션 속도를 늦추려면 DevTools의 Inspector 뷰에서 Slow Animations 버튼을 클릭하세요. 이 기능은 애니메이션 속도를 20%로 줄여줍니다. 더 세밀하게 조절하고 싶다면 다음과 같이 하세요.

 

scheduler 라이브러리의 timeDilation 변수를 1.0보다 큰 값으로 설정하세요. 예를 들어 50.0으로 설정하면 애니메이션이 매우 느리게 실행됩니다. 이 값은 앱이 시작될 때 한 번만 설정하는 것이 가장 좋습니다. 실행 중에 변경하면(특히 애니메이션이 실행 중일 때 값을 줄이면), 프레임워크가 시간이 거꾸로 흐른다고 인식할 수 있으며, 이는 assert 오류를 유발하거나 디버깅을 방해할 수 있습니다.


 

성능 문제 디버깅

주의 : DevTools를 사용해도 유사한 결과를 얻을 수 있습니다. 일부 디버그 플래그는 실질적인 이점이 적습니다. DevTools에 추가하고 싶은 기능이 있는 플래그가 있다면, 이슈를 등록하세요.

 

Flutter는 개발 주기의 다양한 지점에서 앱을 디버깅할 수 있도록 다양한 최상위 속성과 함수를 제공합니다. 이러한 기능을 사용하려면 앱을 디버그 모드로 컴파일해야 합니다.

 

다음은 성능 문제를 디버깅하기 위해 렌더링 라이브러리에서 제공하는 몇 가지 플래그 및 하나의 함수입니다:

debugDumpRenderTree()

렌더링 트리를 콘솔에 덤프하려면, 레이아웃 또는 리페인트 단계가 아닐 때 이 함수를 호출하세요.

 

다음과 같이 플래그를 설정할 수 있습니다:

  • 프레임워크 코드를 편집하거나,
  • 모듈을 import하고 main() 함수에서 값을 설정한 후 hot restart 하세요.

 

debugPaintLayerBordersEnabled

각 레이어의 경계를 표시하려면 이 속성을 true로 설정하세요. 설정되면 각 레이어는 경계 상자를 그립니다.

 

debugRepaintRainbowEnabled

각 위젯 주위에 색상 테두리를 표시하려면 이 속성을 true로 설정하세요. 앱 사용자가 스크롤할 때 이 테두리는 색이 바뀝니다. 이 플래그를 설정하려면 앱의 최상위 속성에 debugRepaintRainbowEnabled = true;를 추가하세요. 플래그를 설정한 후에도 정적인 위젯이 색상 변화를 반복한다면, 해당 영역에 repaint boundaries를 추가하는 것을 고려하세요.

 

debugPrintMarkNeedsLayoutStacks

앱이 예상보다 많은 레이아웃을 생성하는지 확인하려면 이 속성을 true로 설정하세요. 이 레이아웃 문제는 타임라인, 프로파일, 또는 레이아웃 메서드 내부의 print 문에서 발생할 수 있습니다. 설정되면, 프레임워크는 각 렌더 객체가 레이아웃 대상으로 표시된 이유를 설명하는 스택 트레이스를 콘솔에 출력합니다.

 

debugPrintMarkNeedsPaintStacks

앱이 예상보다 많이 페인팅되는지 확인하려면 이 속성을 true로 설정하세요.

 

직접 스택 트레이스를 생성할 수도 있습니다. 직접 스택 트레이스를 출력하려면, debugPrintStack() 함수를 앱에 추가하세요.


Dart 코드 성능 추적

참고 : DevTools의 Timeline events 탭을 사용하여 트레이스를 수행할 수 있습니다. DevTools에서 생성된 파일만 Timeline 뷰에 가져오기 및 내보내기가 가능합니다.

 

원하는 Dart 코드 구간의 성능을 추적하고 벽시계 시간 또는 CPU 시간을 측정하려면 dart:developer의 Timeline 도구를 사용하세요.

1. 소스 코드를 엽니다.

2. 측정하려는 코드를 Timeline 메서드로 감쌉니다:

import 'dart:developer';

void main() {
  Timeline.startSync('interesting function');
  // iWonderHowLongThisTakes();
  Timeline.finishSync();
}

3. 앱에 연결한 상태에서 DevTools의 Timeline events 탭을 엽니다.

4. Performance settings에서 Dart recording 옵션을 선택합니다.

5. 측정하고 싶은 함수를 실행합니다.

 

최종 제품과 최대한 비슷한 런타임 성능 특성을 보장하려면 profile 모드에서 앱을 실행하세요.


성능 오버레이 추가

참고 : Flutter Inspector의 Performance Overlay 버튼을 사용하여 앱에서 성능 오버레이 표시를 전환할 수 있습니다. 코드로 설정하고 싶다면 아래 지침을 따르세요.

 

PerformanceOverlay 위젯을 코드에서 활성화하려면 MaterialApp, CupertinoApp, 또는 WidgetsApp 생성자에서 showPerformanceOverlay 속성을 true로 설정하세요:

 

예제 10:

import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      showPerformanceOverlay: true,
      title: 'My Awesome App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
      ),
      home: const MyHomePage(title: 'My Awesome App'),
    );
  }
}

 

만약 MaterialApp, CupertinoApp, 또는 WidgetsApp을 사용하지 않는다면, 앱을 Stack으로 감싸고 그 안에 PerformanceOverlay.allEnabled()를 호출하여 생성된 위젯을 추가하면 동일한 효과를 얻을 수 있습니다.

 

오버레이 그래프 해석 방법을 알고 싶다면 Profiling Flutter performance의 성능 오버레이 항목을 참고하세요.


 

위젯 정렬 그리드 추가

앱에 Material Design 기준선 그리드(baseline grid) 오버레이를 추가하여 정렬을 확인하려면 MaterialApp 생성자에 debugShowMaterialGrid 인자를 추가하세요:

MaterialApp(
  debugShowMaterialGrid: true,
  // ...기타 설정
)

 

Material이 아닌 앱에 오버레이를 추가하려면 GridPaper 위젯을 사용하세요:

Stack(
  children: [
    MyAppContent(),
    GridPaper(
      color: Colors.red.withOpacity(0.2),
      divisions: 2,
      interval: 8.0,
      subdivisions: 1,
    ),
  ],
);
반응형

'Flutter > 디버깅' 카테고리의 다른 글

플러터 앱 디버깅 도구  (0) 2025.04.21
댓글
공지사항