이전 글 - 달력 만들기

 

[Flutter] 메모 앱 만들기 - 달력

전에 React 로 메모를 작성하고, 완료한 메모를 인증할 수 있도록 하는 기능완료한 수에 따라 경험치를 부여하여 메모 작성과 완료에 대한 동기를 부여하는 웹 앱을 만들어 본 경험이 있다.해당

chunws13.tistory.com

이전 글에서는 달력을 만들어 보고, 선택한 날짜를 하이라이트 하는 기능을 간단하게 구현해 보았다.

이번 글에서는

1 .달력 화면에서 버튼을 클릭하면 

2. 메모를 작성하는 화면이 나오고

3. 메모를 작성하고 저장하면 메모 내용을 이전 화면으로 돌려주는 기능을 만들어 본다.

 

1. 달력 화면에 버튼 추가하기

메모를 입력하는 방법은 다양한 방법이 있겠으나,

메모 입력 시 반복 설정 등 다양한 옵션을 추가할 예정이므로 새로운 화면에서 설정하는 것이 더 좋을 것이라고 판단하여

새로운 화면에서 메모를 작성할 수 있도록 기획했다. 이를 위해 달력에 플로팅 버튼을 추가하기 위해 기존 달력 코드에

아래와 같이 추가했다.

class _MyHomePageState extends State<MyHomePage> {
  DateTime _focusedDay = DateTime.now();
  DateTime _selectedDay = DateTime.now();

  void _onDaySelected(DateTime selecetedDay, DateTime focusedDay){
    setState(() {
      _selectedDay = selecetedDay;
      _focusedDay = focusedDay;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("메모 앱"),
      ),
      body: Center(
        child: Column(
          children: [
            TableCalendar(
              firstDay: DateTime.utc(2010, 10, 16),
              lastDay: DateTime.utc(2030, 3, 14),
              focusedDay: _focusedDay,
              selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
              onDaySelected: _onDaySelected,
            ),
          ],
        ),
      ),
      // 플로팅 버튼 추가하는 코드
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}

플로팅 버튼이 추가된 화면

플로팅 버튼을 추가했으나, 버튼을 클릭해도 아무런 반응이 없다.

이는 onPressed 옵션에 빈 함수를 넣어주었기 때문이다.

새로운 화면을 열기 위한 코드를 추가함과 동시에, 메모를 입력할 UI 를 구성해 본다.

 

2. 메모를 작성하는 화면

// _MyHomePageState 중 플로팅 버튼 코드

floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.push(
            context, 
            MaterialPageRoute(builder: (context) => const NewWidget()
            )
          );
        },
        child: const Icon(Icons.add),
      ),
      

// 메모 작성 위젯
class NewWidget extends StatefulWidget {
  const NewWidget({super.key});

  @override
  State<NewWidget> createState() => _NewWidgetState();
}

class _NewWidgetState extends State<NewWidget> {
  final _formKey = GlobalKey<FormState>();
  final TextEditingController _contentControler = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("새 메모 만들기"),
          ),
        body: Form(
          key : _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _contentControler,
                decoration: const InputDecoration(labelText: "오늘 할 일"),
              ElevatedButton(
                onPressed: (){},
                child: const Text("저장"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

먼저, 캘린더 위젯에 onPressed 함수를 완성한다. Navigator.push 를 통해 새로운 위젯을 연다.

그 다음, newWidgetState에서 메모 위젯을 구현해주면 된다.

TextFormField (입력이 이뤄지는 창)를 만든 후, controller, decoration (placeholder와 유사한 역할)을 구현

ElvatedButton으로 제출 버튼을 만들어 주었다.

메모 작성 위젯 예시 화면

이 상태에서도 입력은 되긴 하지만, 어떤 기능도 들어가 있지 않는 화면이다. 이제 이 화면에 기능을 추가해야 한다,

먼저, 메모 입력 란에서 저장 신호가 오는 경우 어떻게 행동할지 지정해야 한다.

class _NewWidgetState extends State<NewWidget> {
  final _formKey = GlobalKey<FormState>();

  String? _content;

  final TextEditingController _contentControler = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("새 메모 만들기"),
          ),
        body: Form(
          key : _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _contentControler,
                decoration: const InputDecoration(labelText: "오늘 할 일"),
                onSaved: (String? value) => _content = value,
                validator: (String? value) {
                  if (value == null || value.isEmpty) {
                    return "1자 이상 입력하세요";
                  }
                  return null;
                },
              ),
              //이하 생략

_content를 먼저 지정하여 메모 내용을 저장할 변수를 만든다.

그 다음 TextFormField에 저장 신호가 들어오면 (onSaved) 텍스트 필드에 있는 value 값을 _content에 저장한다.

validator은 선택 사항으로, 특정 조건을 만족하지 않는 경우에 입력창 하단에 경고 메세지를 반환하는 역할을 한다.

저장 신호가 발생하면, 메모를 저장하는 것 까지 구현이 되었으니, 이제 EvlatedButton을 통해 저장 시그널을 보내는 함수를 작성한다.

 

class _NewWidgetState extends State<NewWidget> {
  final _formKey = GlobalKey<FormState>();

  String? _content;

  final TextEditingController _contentControler = TextEditingController();
  
  // 메모를 저장하는 함수
  void _contentSumbit() {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      Navigator.pop(context, _content);
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("새 메모 만들기"),
          ),
        body: Form(
          key : _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _contentControler,
                decoration: const InputDecoration(labelText: "오늘 할 일"),
                onSaved: (String? value) => _content = value,
                validator: (String? value) {
                  if (value == null || value.isEmpty) {
                    return "1자 이상 입력하세요";
                  }
                  return null;
                },
              ),
              ElevatedButton(
                onPressed: _contentSumbit,
                child: const Text("저장"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

_contentSubmit 함수를 통해 메모 저장 시그널을 보낸다.

해당 함수 가장 첫 줄에 등장하는 validate() 는 _formKey로 등록된 위젯에 대한 입력을 검증한다.

 다음 _formKey에 입력된 내용을 저장한다. 이 때 TextFormField의  onSave 로 인해 _content 변수에 내용이 저장된다.

그 다음 이전 화면으로 돌아가는 기능인 Navigator.pop를 사용하면서, 저장한 메모인 _content를 추가해서 반환한다.

 

3. 저장된 메모를 캘린더 위젯에서 확인하기

저장된 메모를 확인하기 위해서는 캘린더 위젯 > 플로팅 버튼 클릭에 대한 함수를 수정해야 한다.

이전 코드에서는 단순히 화면을 여는 동작으로만 구현했으나, 이제는 반환되는 값이 있으므로 

플로팅 버튼 위젯의 onPressed 를 아래와 같이 수정한다.

floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final result = await Navigator.push(
            context, 
            MaterialPageRoute(builder: (context) => const NewWidget()
            )
          );
          if (result != null) {
            print(result);
          } 
        }

새로운 위젯 (메모) 이 저장되었을 때 반환하는 값을 result 변수에 할당한다.

그 후, result 값이 null이 아닌 경우에 출력해보도록 하고 테스트를 진행했다 (123 입력 한 결과)

메모 저장 결과, 123이 출력 되는 것을 확인

정상적으로 출력되는 것이 확인되었다.

 

4. 전체 코드

import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  DateTime _focusedDay = DateTime.now();
  DateTime _selectedDay = DateTime.now();

  void _onDaySelected(DateTime selecetedDay, DateTime focusedDay){
    setState(() {
      _selectedDay = selecetedDay;
      _focusedDay = focusedDay;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("메모 앱"),
      ),
      body: Center(
        child: Column(
          children: [
            TableCalendar(
              firstDay: DateTime.utc(2010, 10, 16),
              lastDay: DateTime.utc(2030, 3, 14),
              focusedDay: _focusedDay,
              selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
              onDaySelected: _onDaySelected,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final result = await Navigator.push(
            context, 
            MaterialPageRoute(builder: (context) => const NewWidget()
            )
          );
          if (result != null) {
            print(result);
          } 
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

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

  @override
  State<NewWidget> createState() => _NewWidgetState();
}

class _NewWidgetState extends State<NewWidget> {
  final _formKey = GlobalKey<FormState>();

  String? _content;

  final TextEditingController _contentControler = TextEditingController();

  void _contentSumbit() {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      Navigator.pop(context, _content);
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("새 메모 만들기"),
          ),
        body: Form(
          key : _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _contentControler,
                decoration: const InputDecoration(labelText: "오늘 할 일"),
                onSaved: (String? value) => _content = value,
                validator: (String? value) {
                  if (value == null || value.isEmpty) {
                    return "1자 이상 입력하세요";
                  }
                  return null;
                },
              ),
              ElevatedButton(
                onPressed: _contentSumbit,
                child: const Text("저장"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

다음 글에서는 저장된 메모를 캘린더 아래에 표기하는 방법에 대해서 작성해 보자.