inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

[코드팩토리] [초급] Flutter 3.0 앱 개발 - 10개의 프로젝트로 오늘 초보 탈출!

섹션 21. 캘린더 스케쥴러 오류 문의드립니다.

507

최우인

작성한 질문수 2

0

색상 상태관리 부분을 듣고 있습니다. 똑 같이 따라했는데요.

아래 내용과 같이 FutureBuilder를 사용하여 똑같이 따라 했는데도 불구하고 null이 리턴되어 오류가 납니다.

future: GetIt.I<LocalDatabase>().getCategoryColors() 에서 강사님 강의에서는 데이터를 가지고 오는데, 제가 만든 코드에서는 null이 리턴되네요. 혹시 몰라 main.dart에서 GetIt.I<LocalDatabase>().getCategoryColors() 를 실행해 보고, 결과를 보면 정상적인 데이터가 들어옵니다.

뭐가 문제일까요?

main.dart

import 'package:calendar_schedule_exam/database/drift_database.dart';
import 'package:calendar_schedule_exam/screen/home_screen.dart';
import 'package:drift/drift.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:intl/date_symbol_data_local.dart';

const DEFAULT_COLORS = [
  // 빨강
  'F44336',
  // 주황
  'FF9800',
  // 노랑
  'FFEB3B',
  // 초록
  'FCAF50',
  // 파랑
  '2196F3',
  // 남
  '3F51B5',
  // 보라
  '9C27B0',
];

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await initializeDateFormatting();

  final database = LocalDatabase();
  GetIt.I.registerSingleton<LocalDatabase>(database);
  final result = GetIt.I<LocalDatabase>().getCategoryColors();

  final colors = await database.getCategoryColors();
  if (colors.isEmpty) {
    for (String hexCode in DEFAULT_COLORS) {
      await database.createCategoryColor(
        CategoryColorsCompanion(
          hexCode: Value(hexCode),
        ),
      );
    }
  }

  runApp(MaterialApp(
    theme: ThemeData(
      fontFamily: 'NotoSans',
    ),
    home: HomeScreen(),
  ));
}

schedule_bottom_sheet.dart

import 'package:calendar_schedule_exam/component/custom_text_field.dart';
import 'package:calendar_schedule_exam/const/colors.dart';
import 'package:calendar_schedule_exam/database/drift_database.dart';
import 'package:calendar_schedule_exam/model/category_color.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';

class ScheduleBottomSheet extends StatefulWidget {
  const ScheduleBottomSheet({super.key});

  @override
  State<ScheduleBottomSheet> createState() => _ScheduleBottomSheetState();
}

class _ScheduleBottomSheetState extends State<ScheduleBottomSheet> {
  final GlobalKey<FormState> formKey = GlobalKey();
  int? startTime;
  int? endTime;
  String? content;
  int? selectedColorId;

  @override
  Widget build(BuildContext context) {
    final bottomInset = MediaQuery.of(context).viewInsets.bottom;
    return SafeArea(
      bottom: true,
      child: GestureDetector(
        onTap: () => FocusScope.of(context).requestFocus(FocusNode()),
        child: Container(
          height: MediaQuery.of(context).size.height / 2 + bottomInset,
          color: Colors.white,
          child: Padding(
            padding: EdgeInsets.only(bottom: bottomInset),
            child: Padding(
              padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 16.0),
              child: Form(
                key: formKey,
                autovalidateMode: AutovalidateMode.always,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    _Time(
                      onStartSaved: (newValue) {
                        startTime = int.parse(newValue!);
                      },
                      onEndSaved: (newValue) {
                        endTime = int.parse(newValue!);
                      },
                    ),
                    SizedBox(height: 16.0),
                    _Content(
                      onSaved: (newValue) {
                        content = newValue;
                      },
                    ),
                    SizedBox(height: 16.0),
                    FutureBuilder<List<CategoryColor>>(
                        future: GetIt.I<LocalDatabase>().getCategoryColors(),
                        builder: (context, snapshot) {
                          print(snapshot.data);
                          if (snapshot.hasData &&
                              selectedColorId == null &&
                              snapshot.data!.isNotEmpty) {
                            selectedColorId = snapshot.data![0].id;
                          }
                          return _ColorPicker(
                            colors: snapshot.hasData ? snapshot.data! : [],
                            selectColorId: selectedColorId!,
                          );
                        }),
                    SizedBox(height: 16.0),
                    _SaveButton(
                      onPressed: onSavePressed,
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }

  void onSavePressed() {
    if (formKey.currentState == null) {
      return;
    }

    if (formKey.currentState!.validate()) {
      formKey.currentState!.save();
    } else {
      print('에러가 있습니다.');
    }
  }
}

class _Time extends StatelessWidget {
  final FormFieldSetter<String> onStartSaved;
  final FormFieldSetter<String> onEndSaved;

  const _Time(
      {super.key, required this.onStartSaved, required this.onEndSaved});

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Expanded(
            child: CustomTextField(
          label: '시작 시간',
          isTime: true,
          onSaved: onStartSaved,
        )),
        SizedBox(width: 16.0),
        Expanded(
          child: CustomTextField(
            label: '마감 시간',
            isTime: true,
            onSaved: onEndSaved,
          ),
        ),
      ],
    );
  }
}

class _Content extends StatelessWidget {
  final FormFieldSetter<String> onSaved;

  const _Content({super.key, required this.onSaved});

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: CustomTextField(
        label: '내용',
        isTime: false,
        onSaved: onSaved,
      ),
    );
  }
}

class _ColorPicker extends StatelessWidget {
  final List<CategoryColor> colors;
  final int selectColorId;

  const _ColorPicker(
      {super.key, required this.colors, required this.selectColorId});

  @override
  Widget build(BuildContext context) {
    return Wrap(
      spacing: 8.0,
      runSpacing: 10.0,
      children: colors.map((e) => rendColor(e, selectColorId == e.id)).toList(),
    );
  }

  Widget rendColor(CategoryColor color, bool isSelected) {
    return Container(
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          color: Color(int.parse('FF${color.hexCode}', radix: 16)),
          border:
              isSelected ? Border.all(color: Colors.black, width: 1.0) : null,
        ),
        height: 32,
        width: 32);
  }
}

class _SaveButton extends StatelessWidget {
  final VoidCallback onPressed;

  const _SaveButton({super.key, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: double.infinity,
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: PRIMARY_COLOR,
        ),
        onPressed: onPressed,
        child: Text('Save'),
      ),
    );
  }
}

drift_database.dart

import 'dart:io';

import 'package:calendar_schedule_exam/model/category_color.dart';
import 'package:calendar_schedule_exam/model/schedule.dart';
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

part 'drift_database.g.dart';

@DriftDatabase(
  tables: [
    Schedules,
    CategoryColors,
  ],
)
class LocalDatabase extends _$LocalDatabase {
  LocalDatabase() : super(_openConnection());

  // insert schedules
  Future<int> createSchedule(SchedulesCompanion data) => into(schedules).insert(data);
  // insert categoryColors
  Future<int> createCategoryColor(CategoryColorsCompanion data) => into(categoryColors).insert(data);
  // select all categoryColors
  Future<List<CategoryColor>> getCategoryColors() => select(categoryColors).get();

  @override
  // TODO: implement schemaVersion
  int get schemaVersion => 1;
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(p.join(dbFolder.path, 'db.sqlite'));
    return NativeDatabase(file);
  });
}

오류내용

Launching lib/main.dart on iPhone 14 Pro Max in debug mode...
Running Xcode build...
Xcode build done.                                            7.8s
[VERBOSE-2:FlutterDarwinContextMetalImpeller.mm(35)] Using the Impeller rendering backend.
Debug service listening on ws://127.0.0.1:61505/HeTRp4ZQphA=/ws
Syncing files to device iPhone 14 Pro Max...
flutter: null

======== Exception caught by widgets library =======================================================
The following _TypeError was thrown building FutureBuilder<List<CategoryColor>>(dirty, state: _FutureBuilderState<List<CategoryColor>>#41541):
Null check operator used on a null value

The relevant error-causing widget was: 
  FutureBuilder<List<CategoryColor>> FutureBuilder:file:///Users/choiwooin/dev/flutterProject/calendar_schedule_exam/lib/component/schedule_bottom_sheet.dart:57:21
When the exception was thrown, this was the stack: 
#0      _ScheduleBottomSheetState.build.<anonymous closure> (package:calendar_schedule_exam/component/schedule_bottom_sheet.dart:68:59)
#1      _FutureBuilderState.build (package:flutter/src/widgets/async.dart:612:55)
#2      StatefulElement.build (package:flutter/src/widgets/framework.dart:5198:27)
#3      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5086:15)
#4      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5251:11)
#5      Element.rebuild (package:flutter/src/widgets/framework.dart:4805:7)
#6      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:5068:5)
#7      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5242:11)

 

flutter 클론코딩

답변 2

2

코드팩토리

안녕하세요!

데이터 마이그레이션이 잘못 됐을 수 있습니다.

에뮬레이터/시뮬레이터에서 앱을 삭제한 후 다시 실행해보세요!

그래도 안되면 다시 질문 부탁드립니다.

감사합니다!

0

심민규

와 저도 앱삭제하고 다시하니 문제가 해결됬어요! 감사합니다.

0

최우인

앱 다 지우고 터미널에서 Flutter Clean 한 다음 해보니까 되네요. 감사합니다. 열공하겠습니다.

198강 (){onTap(e);}의 이해 돕기

0

26

1

video_call 플러그인 설치후 에러 발생

0

45

1

SDK 안드로이드 설치 질문!

0

60

1

코드팩토리 디스코드 링크 다시 부탁드려요~

0

92

1

Webview를 이용해서 URL 상의 페이지 출력 불가

0

70

1

홈스크린 함수를 함축해서 main.dart에 옮기는 문제

0

55

1

플레이스토어

0

59

1

아고라 엔진 init 함수의 반환타입이 Future<void> 이것의 의미는 무엇인가요?

0

55

1

가이드라인 질문

0

57

0

emulator 에러 환경설정 뭐가 문제 일까요??

0

76

1

emulator 실행 오류

0

93

3

Column을 가로방향 최대 사이즈를 차지하도록 하는 방법에 관련

0

71

1

pubspec.yaml에서 font를 추가하면서 weight 값을 지정하는 것이 의미가 있는 것인지 문의

0

43

1

setState()를 호출하지 않으면 build가 실행 안되는 건가요?

0

53

1

video_call 플러그인 설치시 에러문제

0

64

1

children 안의 if 문에서 { } 못쓰는 이유?

0

48

1

이렇게 오류가 떠요

0

63

1

AppBar 사용했는데

0

61

2

[문제해결] '오늘도 출첵!' 의 171번 강의에서 중요한 문제를 발견했습니다

0

56

1

StatefulWidget 실습 에러가 발생합니다.[해결완료]

0

63

1

Video Player 프로젝트에 대한 추가 질문

0

53

0

Row위젯이나 column위젯의 위치는 누가 정하나요??

0

42

1

geolocator 오류때문에 개발진행이 불가능입니다

0

63

1

API 관련 이슈

0

86

2