ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Flutter | 간단 메모 앱 만들어보고 광고까지 달아서 배포하기 - 4. 목록 화면과 메모 작성 화면 연결하기
    개발/Flutter 2023. 2. 14. 18:28
    반응형

    메모 작성 화면까지 만들어 봤는데 이번에는 메인 목록 화면을 home에 띄워주고 floating action button을 누르면 메모작성화면이 나오도록 연결해 보겠습니다. 또한 작성된 노트를 삭제하거나 편집이 가능하도록 해보겠습니다.

    여기서 사용할 방법은 각화면에 이름을 붙이고 이 이름을 사용해서 원하는 화면으로 이동하는 방법입니다. 각화면에 이름을 붙여보겠습니다. note_list_page에서 상의 NoteListPage의 class 안에 routeName이라는 상수를 만들어 줍니다. 기본페이지이기 때문에 /만 있게 해주시면 됩니다.

    static const routeName = '/';

    다음은 note_edit_page에 이름을 edit으로 지정해 주시면 됩니다.

    static const routeName = '/edit';

    화면이 2개이기 때문에 화면 이름은 끝이고 초기화면 지정과 화면을 맵핑해주겠습니다. main에서 MaterialApp 위젯 안에 속성에 initialRoute가 있습니다. 여기에 NoteListPage.routeName을 넣어주시면 됩니다. 하지만 initialRoute는 home 속성과 같이 쓸 수 없기때문에 home은 삭제해 주겠습니다. routes 속성을 사용해주시면 됩니다. {}안에 해당 routeName일때 띄워주는 페이지를 입력해주시면 됩니다.

    routes: {
      NoteListPage.routeName: (context) => const NoteListPage(),
      NoteEditPage.routeName: (context) => const NoteEditPage(),
    },

    현재까지 만든 페이지들은 다 넣어줬으니 우선 편집과 삭제가 가능한 페이지를 만들어 주겠습니다. page 폴더에 note_view_page.dart를 만들어 주겠습니다. StatefulWidget인 NoteViewPage라는 클래스를 만들어 주겠습니다. 우선 routeName를 /view로 해줍니다. 그리고 해당 메모를 보기위한 index를 받아오는 공간을 만들어 주겠습니다. 그리고 클래스가 생성될때 함께 보낼 생성자를 생성해 줍니다. (그냥 생성해도 되고, 저 stf로 자동 생성하신 분은 const NoteViewPage({Key? key, required this.index}) : super(key: key); 이렇게 빨간줄에서 alt + enter로 추가해주시면 됩니다.)  그리고 화면 구성을 위한 노트 정보를 받아오기 위해 _NoteViewPageState에 noteManager로 받아오겠습니다. build 위젯 안에 return 안에 final note로 getNote를 해줍니다. 이때 인덱스는 상단에서 선언한 index를 사용하는데 이때 사용법은 widget.index라고 해주시면 됩니다. 

    final note = noteManager().getNote(widget.index);

    노트의 제목은 AppBar에 나타내주겠습니다. Scaffold 안에 AppBar의 Title에 note의 title이 없으면 (제목없음), 있다면 note.title를 보여주시면 되겠습니다.

    appBar: AppBar(
      title: Text(note.title.isEmpty?'(제목없음)':note.title),
    ),

    그리고 삭제와 편집버튼을 만들어 주겠습니다. actions에 IconButton을 2개 만들어주시면 됩니다. icon에는 위에는 edit, 아래는 delete로 만들어 주시고 tooltip은 편집, 삭제로 해주시면 됩니다. onPressed는 나중에 처리해주겠습니다. 이제 body를 작성해주면 되는데 노트를 생성하는 부분과 똑같이 스크롤 될 수 있도록 SizedBox.expand를 만들어 주고 SingleChildScrollView도 만들어 준뒤, TextField였던 부분만 Text로 note.body로 받아와 줍니다. view 페이지의 화면 구성은 끝이 났고, edit화면으로 넘겨주는 함수를 만들어 보겠습니다. 하단데 _edit라는 void함수를 만들어 주고 해당 페이지의 index도 같이 넘겨줘야 하기때문에 인자에 int index를 추가해 줍니다. 함수안에 페이지를 넘겨주는 코드를 넣어주는데 routeName으로 넘겨줄때는 Navigator의 pushNamed를 사용합니다. context와 이동하려는 페이지의 routeName를 넘겨주시면 됩니다. 그리고 index 정보도 같이 넘겨줘야 하는데 arguments 속성에 index를 같이 넘겨주시면 됩니다.

    void _edit(int index) {
      Navigator.pushNamed(
        context,
        NoteEditPage.routeName,
        arguments: index,
      );
    }

    다음은 수정이 완료된 이후 다시 현재 페이지로 돌아왔을때 변경된 내용을 갱신해주는 부분이 필요합니다. 이 부분은 pushNamed 뒤에 then을 추가해주면 됩니다. 그리고 그 안에 화면을 갱신해주는 setState를 넣어주시면 됩니다.

    void _edit(int index) {
      Navigator.pushNamed(
        context,
        NoteEditPage.routeName,
        arguments: index,
      ).then((_) {
        setState(() {});
      });
    }

    편집은 끝이고 이제 삭제함수를 만들어 보겠습니다. 우선 삭제하기전에는 한번 더 물어보고 삭제 해주도록 하겠습니다. 함수명은 _confirmDelete로 해주시면 됩니다. 삭제에 필요한 부분이 index 이므로 int index를 인자로 넣어주시면 됩니다. 이제 새로운 창을 띄우겠습니다. showDialog를 이용해서 삭제 확인을 해주시면 됩니다. context는 이 페이지의 context를 그대로 사용하시면 되고 builder은 (context){}로 사용해주시면 됩니다. retrun 값으로 AlertDialog를 사용하겠습니다. 제목은 노트삭제로 해주시고 context에는 노트를 삭제 할까요? 라는 Text를 넣어주시면 됩니다. 그리고 이제 버튼을 만들어 줄텐데 AppBar와 같이 actions에 사용하시면 됩니다. 우선 취소 버튼부터 TextButton으로 만들어 주시면 됩니다. child에 Text로 아니오로 취소버튼을 만들어 주시고 onPressed에 (){}안에 Dialog가 그냥 꺼지도록 Navigator.pop(context);를 해주시면 됩니다. 확인 버튼은 Text로 예로 만들어 주시면 되고 onPressed안에는 삭제하는 함수인 noteManager의 deleteNote(index)를 사용해주시면 됩니다. 삭제가 된 후 Dialog를 닫고 첫 페이지로 이동해야 하는데 이때는 Navigator의 popUntil을 사용해주시면 됩니다. context는 그대로 사용하시요 route를 isFirst로 해주시면 첫 페이지로 이동합니다.

    void _confirmDelete(int index) {
      showDialog(
        context: context,
        builder: (context) {
          return AlertDialog(
            title: Text('노트 삭제'),
            content: Text('노트를 삭제 할까요?'),
            actions: [
              TextButton(
                onPressed: () {
                  Navigator.pop(context);
                },
                child: Text('아니오'),
              ),
              TextButton(
                onPressed: () {
                  noteManager().deleteNote(index);
                  Navigator.popUntil(context, (route) => route.isFirst);
                },
                child: Text('예'),
              ),
            ],
          );
        },
      );
    }

    이제 AppBar의 버튼의 onPressed에 각 함수를 넣어주시면 됩니다. index를 넣어야 할 부분은 현재 페이지 index인 widget.index로 넣어주시면 됩니다. 이 페이지도 main에 화면경로를 추가해주시면 됩니다. View페이지는 index를 넣어줘야 하는데 여기서 index는 argument로 받은 값을 받아와야 합니다. ModalRoute로 받아 올 수 있습니다.

    NoteViewPage.routeName: (context) {
      final index = ModalRoute.of(context)!.settings.arguments as int;
      return NoteViewPage(index: index);
    },

    위와 같이 index를  arguments로 받고 int로 변환해줘서 해주시면 됩니다.

    이제 목록 화면에 새노트 작성 버튼을 만들고 작성 화면과 연결해 보겠습니다. 첫화면이 목록 페이지이므로 NoteListPage의 Scaffold에 floatingActionButton 속성에 FloatingActionButton을 추가해주시면 됩니다. child에는 Icon으로 add 아이콘을 추가해 주시고 tooltip에는 새 노트 라고 만들어 주시면 됩니다. 이제 onPressed에서 페이지를 이동시켜 주면 됩니다. Navigator의 pushNamed를 사용하시면 됩니다. 이동 페이지는 NoteEditPage의 routeName를 사용하시면 됩니다. 그리고 이때 Edit 페이지에서 작성이 완료되면 목록을 갱신해 줘야 하므로 then에 setState를 해주시면 됩니다. 그리고 edit 페이지에서 _saveNote 함수에 저장 성공시 창을 꺼주는 Navigator.pop을 해주시면 됩니다.

     

    생성이 잘되는것을 볼 수 있습니다. 이제 편집이 가능하도록 구현해 보겠습니다. edit 페이지에서 해보겠습니다. 그렇다면 index가 필요한데 생성에는 필요가 없습니다. 그래서 필요할때만 index를 받아오도록 해서 구현해 보겠습니다. 그래서 NoteEditPage 클래서에 final로 index를 만들지만 null 값을 허용하도록 final int? index로 만들어 주시면 됩니다. 그러면 생성자에는 아까와는 다르게 required가 빠진 this.index가 생성이 된것을 확인할 수 있습니다. 그리고 만약 편집모드로 들어온다면 제목과 내용 색상을 가져와야합니다. 가져와야할 시기는 페이지가 처음으로 생성되는 때인 initState에서 가져오도록 하겠습니다. _NoteEditPageState의 build 위젯의 윗부분에서 initState를 생성해주시면 됩니다. 자동생성으로 보시면 super 함수를 먼저 불러오는데 initState 함수에서 기본으로 수행하는 동작들을 수행합니다. 그리고 noteIdex에 위에서 불러온 widget.index를 담아주고 noteIndex가 비어있지 않다면 noteManager를 통해서 title, body, color를 넣어주시면 됩니다. 이제 저장부분을 수정해 보겠습니다. 현재 편집모드에서 저장을해도 새로 추가가되는 형태이기 때문에 이부분을 수정해주겠습니다. 우선 if문 안에 저장되는 Note를 final note에 저장해둡니다. 그리고 noteIndex를 widget.index에서 받아오고 이 값이 null이 아니라면 updateNote로 갱신 시켜주고 아니라면 addNote 해주시면 됩니다. 

    final note = Note(
      bodyController.text,
      title: titleController.text,
      color: memoColor,
    );
    final noteIndex = widget.index;
    if (noteIndex != null) {
      noteManager().updateNote(noteIndex, note);
    } else {
      noteManager().addNote(note);
    }

    이제 편집이 될 수 있게 변경 했으므로 화면을 호출할때 index를 넘겨주도록 변경해줘야 합니다. main으로 가서  NoteEditPage를 해주는 부분을 변경해 주겠습니다. 우선 argumnts를 받아와서 해당부분이 null이 아니면 int로 변환해서 index로 넘겨주고 아니면 null로 넘겨주도록 하겠습니다. 즉 argument에 값이 있다면 index가 있는것이므로 편집모드로, null이라면 새 노트 추가 모드로 들어가는 것입니다.

    NoteEditPage.routeName: (context) {
      final args = ModalRoute.of(context)!.settings.arguments;
      final index = args != null  ? args  as int : null;
      return NoteEditPage(index: index,);
    },

    이로써 편집화면 수정은 끝이났고 노트 목록에서 View 페이지로 이동할 수 있도록 해주겠습니다. NoteListPage에서 _buildCard로 이동해주겠습니다. 현재는 Card에서는 onTap이나 onPressed를 할 수가 없습니다. 이를 위해 Card를 InkWell로 감싸서 클릭 시 동작이 가능해지도록 변경해주겠습니다.그리고 onTap에 Navigator.pushNamed로 이동시켜주겠습니다. ViewPage.routeName로 이동시켜주고 arguments에 index를 추가해줍니다. 그리고 index를 받아오기 위해서 _buildCard에 인자로 int index를 추가해줍니다. 그 후 페이지로 다시 돌아왔을때 갱신시켜주기 위해서 then에 setState를 추가해 줍니다.

    Widget _buildCard(int index,Note note) {
        return InkWell(
          onTap: (){
            Navigator.pushNamed(context, NoteViewPage.routeName, arguments: index).then((_){
              setState(() {
    
              });
            });
          },
          child: Card(
            color: note.color,
            child: Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    note.title.isEmpty ? '(제목없음)' : note.title,
                    style: const TextStyle(
                      fontSize: 16,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(
                    height: 16,
                  ),
                  Expanded(
                    child: Text(
                      note.body,
                      overflow: TextOverflow.fade,
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }

    이제 목록을 불러오는 _buildCards를 수정해보겠습니다. 우선 notes에 노트목록을 받아옵니다. 그후 return에 List.generate를 활용해서 list의 length 만큼 index를 불러와서 note를 불러올수 있게 사용해 줍니다.

    List<Widget> _buildCards() {
      final notes = noteManager().listNotes();
      return List.generate(
          notes.length, (index) => _buildCard(index, notes[index]));
    }

    이제 새로 실행해서 노트를 작성하고 수정해 보겠습니다.

    잘 작동하는 것을 확인할 수 있습니다. 하지만 현재 어플을 껐다 키면 초기화 되는데 이 어플의 저장공간만을 이용해서 그렇습니다. 다음에는 이 저장을 껐다 켜도 활용할 수 있도록 해결해 보겠습니다.

    반응형

    댓글

Designed by Tistory.