본문 바로가기
  • 인공지능
  • 블록체인
  • 정보보안
코딩 알로하 :: two/하이브리드앱

패스트캠퍼스 챌린지 15일차

by nathan03 2021. 11. 15.
반응형

# 강의 제목 : 누적다운로드 120만+ 1인 개발자와 함께하는 앱 개발 입문 Online

# 강의 목표 : 기초부터 운영까지 앱 개발의 전체 프로세스를 이해한다. 
                  내 이름으로 된 앱을 최대 10개까지 만들어 출시할 수 있다. 
                  앱 개발자로 성장할 수 있는 초석을 다진다. 
                  반응 얻는 앱의 특징과 노하우를 알아간다. 
                  향후 강의 없이도 나만의 앱을 개발할수 있는 실력을 가진다. 

# 강의 요약 : 프로그램 설치부터 기본 문법, 광고 다는 법, 클론코딩을 진행하며 필수 지식을 학습한다. 
                 총 10개의 다른 주제로 실제 사용화 가능한 수준의 앱을 만들어본다.
                 나의 앱을 세상에 선보이기 위한 개발자 등록 및 배포를 진행한다. 
                 강사님의 리뷰/클레임 대응사례 등 앱 성공 포인트를 참고해 1인 개발자로서의 입지를 다진다. 

 # 강의 목차 : Flutter 실전 앱 제작
                    - 앱 기능 및 디자인 설계 및 초기 구조 만들기 (일기앱)
                    - 일기 작성 화면 만들기 
                    - 일기 작성 화면 만들기 (이모티콘 추가) (15일차)
                    - 달력 화면 만들기
                    - 통계, 더보기 화면 만들기
                    - Splash 스크린 추가하기                  

# 강의 화면 : 


# 강의 내용 : Flutter 실전 앱 제작 (일기 작성 화면 만들기 - 이모티콘 추가)

1. 저장하기 이어서 구현하기 

# main.dart

class _MyHomePageState extends State<MyHomePage> {

  int selectIndex = 0;
  final dbHelper = DatabaseHelper.instance;
  Diary todayDiary;

  void getTodayDiary() async{
    List<Diary> diary = await dbHelper.getDiaryByDate(Utils.getFormatTime(DateTime.now()));
    if(diary.isNotEmpty){
      todayDiary = diary.first;
    }
    setState((){

    });
  }

  @override
  void initSate(){
    super.initState();
    getTodayDiary();
  }

주의. initState() 함수 안에서 절대로 setState()함수를 호출 불가능하다. 

2. 저장 후 화면 그리기

# main.dart

import 'package:diary/write.dart';
import 'package:flutter/material.dart';

import 'data/database.dart';
import 'data/diary.dart';
import 'data/util.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  int selectIndex = 0;
  final dbHelper = DatabaseHelper.instance;
  Diary todayDiary;

  List<String> statusImg = [
    "assets/img/icoweather.png",
    "assets/img/icoweather_2.png",
    "assets/img/icoweather_3.png",
  ];


  void getTodayDiary() async{
    List<Diary> diary = await dbHelper.getDiaryByDate(Utils.getFormatTime(DateTime.now()));
    if(diary.isNotEmpty){
      todayDiary = diary.first;
    }
    setState((){

    });
  }

  @override
  void initSate(){
    super.initState();
    getTodayDiary();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(""),
      ),
      body: Container(child: getPage()),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
            await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) => DiaryWritePage(
              diary: Diary(
                date: Utils.getFormatTime(DateTime.now()),
                title: "",
                memo: "",
                status: 0,
                image: "assets/img/b1.jpg"
              ),
            )));
            getTodayDiary();
        },
      tooltip: "Increment",
      child:Icon(Icons.add),
    ),
    bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
              icon: Icon(Icons.today),
              label: "오늘"
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.calendar_today_rounded),
              label: "기록"
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.insert_chart),
              label: "통계"
          ),
        ],
        onTap: (idx){
          setState(() {
            selectIndex = idx;
          });
        },
      ),
    );
  }

  Widget getPage(){
    if (selectIndex == 0){
      return getTodayPage();
    }else if(selectIndex == 1){
      return getHistoryPage();
    }else{
      getChartPage();
    }
  }

  Widget getTodayPage(){
    if(todayDiary == null){
      return Container(
        child: Text("일기 작성 해주세요~~~"),
      );
    }

    return Container(
      child: Stack(
        children: [
          Positioned.fill(
              child: Image.asset(todayDiary.image, fit: BoxFit.cover,),
          ),
          Positioned.fill(
            child: ListView(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text("${DateTime.now().month}.${DateTime.now().day}",
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,
                    color: Colors.white),),
                    Image.asset(statusImg[todayDiary.status], fit: BoxFit.contain,)
                  ],
                )
              ],
            )
          )
        ],
      ),
    );
  }

  Widget getHistoryPage(){
    return Container();
  }

  Widget getChartPage(){
    return Container();
  }
}


# 실행 결과

 

3. 일기 작성 후 화면 구현하기 
# main.dart

import 'package:diary/write.dart';
import 'package:flutter/material.dart';

import 'data/database.dart';
import 'data/diary.dart';
import 'data/util.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  int selectIndex = 0;
  final dbHelper = DatabaseHelper.instance;
  Diary todayDiary;

  List<String> statusImg = [
    "assets/img/icoweather.png",
    "assets/img/icoweather_2.png",
    "assets/img/icoweather_3.png",
  ];


  void getTodayDiary() async{
    List<Diary> diary = await dbHelper.getDiaryByDate(Utils.getFormatTime(DateTime.now()));
    if(diary.isNotEmpty){
      todayDiary = diary.first;
    }
    setState((){

    });
  }

  @override
  void initSate(){
    super.initState();
    getTodayDiary();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(""),
      ),
      body: Container(child: getPage()),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
            Diary _d;
            if(todayDiary != null){
              _d = todayDiary;
            }else{
              _d = Diary(
                  date: Utils.getFormatTime(DateTime.now()),
                  title: "",
                  memo: "",
                  status: 0,
                  image: "assets/img/b1.jpg"
              );
            }
            await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) => DiaryWritePage(
              diary: ,
            )));
            getTodayDiary();
        },
      tooltip: "Increment",
      child:Icon(Icons.add),
    ),
    bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
              icon: Icon(Icons.today),
              label: "오늘"
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.calendar_today_rounded),
              label: "기록"
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.insert_chart),
              label: "통계"
          ),
        ],
        onTap: (idx){
          setState(() {
            selectIndex = idx;
          });
        },
      ),
    );
  }

  Widget getPage(){
    if (selectIndex == 0){
      return getTodayPage();
    }else if(selectIndex == 1){
      return getHistoryPage();
    }else{
      getChartPage();
    }
  }

  Widget getTodayPage(){
    if(todayDiary == null){
      return Container(
        child: Text("일기 작성 해주세요~~~"),
      );
    }

    print(todayDiary.title);
    return Container(
      child: Stack(
        children: [
          Positioned.fill(
              child: Image.asset(todayDiary.image, fit: BoxFit.cover,),
          ),
          Positioned.fill(
            child: ListView(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text("${DateTime.now().month}.${DateTime.now().day}",
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,
                    color: Colors.white),),
                    Image.asset(statusImg[todayDiary.status], fit: BoxFit.contain,)
                  ],
                ),
                Container(
                  child: Column(
                    children: [
                      Text(todayDiary.title, style: TextStyle(fontSize: 18),),
                      Container(height: 12,),
                      Text(todayDiary.memo, style: TextStyle(fontSize: 18),)
                    ]
                  )
                )
              ],
            )
          )
        ],
      ),
    );
  }

  Widget getHistoryPage(){
    return Container();
  }

  Widget getChartPage(){
    return Container();
  }
}

# write.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'data/database.dart';
import 'data/diary.dart';

class DiaryWritePage extends StatefulWidget{

  final Diary diary;

  DiaryWritePage({Key key, this.diary}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return _DiaryWritePageState();
  }
}

class _DiaryWritePageState extends State<DiaryWritePage>{

  List<String> images = [
    "assets/img/b1.jpg",
    "assets/img/b2.jpg",
    "assets/img/b3.jpg",
    "assets/img/b4.jpg",
  ];

  List<String> statusImg = [
    "assets/img/icoweather.png",
    "assets/img/icoweather_2.png",
    "assets/img/icoweather_3.png",
  ];

  int imgIndex = 0;
  TextEditingController nameController = TextEditingController();
  TextEditingController memoController = TextEditingController();

  final dbHelper = DatabaseHelper.instance;

  @override
  void initState(){
    super.initState();
    nameController.text = widget.diary.title;
    memoController.text = widget.diary.memo;
  }

  @override
  Widget build(BuildContext context){
    return Scaffold(
      appBar: AppBar(
        actions: [
          TextButton(child: Text("저장", style: TextStyle(color: Colors.white),),
            onPressed: () async {
              widget.diary.title = nameController.text;
              widget.diary.memo = memoController.text;
              await dbHelper.insertDiary(widget.diary);
              Navigator.of(context).pop();
            })

        ],
      ),
      body: ListView.builder(
          itemBuilder: (ctx, idx){
           if(idx == 0){
             return InkWell(child: Container(
               margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
               width: 100,
               height: 100,
               child: Image.asset(widget.diary.image, fit: BoxFit.cover,),
             ),
              onTap: () {
               setState((){
                widget.diary.image = images[imgIndex];
                imgIndex ++;
                imgIndex = imgIndex % images.length;
              });
              },
             );
           }else if(idx == 1){
             return Container(
               margin: EdgeInsets.symmetric(vertical: 22, horizontal: 16),
               child: Row(
                 mainAxisAlignment: MainAxisAlignment.spaceAround,
                 children: List.generate(statusImg.length, (_idx) {
                   return InkWell(child: Container(
                     height: 70,
                     width: 70,
                     child: Image.asset(statusImg[_idx], fit: BoxFit.contain,),
                     padding: EdgeInsets.all(6),
                     decoration: BoxDecoration(
                       border: Border.all(color: _idx == widget.diary.status ? Colors.blue : Colors.transparent,),
                       borderRadius: BorderRadius.circular(100)
                     ),
                   ), onTap: () {
                     setState(() {
                       widget.diary.status = _idx;
                     });
                   },);
                 }),
               ),
             );
           }
           else if(idx == 2){
             return Container(
               margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
               child: Text("제목", style: TextStyle(fontSize: 20),),
             );
           }
           else if(idx ==3){
             return Container(
               margin: EdgeInsets.symmetric(horizontal: 16),
                child: TextField(
                  controller: nameController,
                ),
             );
           }
           else if(idx == 4){
             return Container(
               margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
               child: Text("내용", style: TextStyle(fontSize: 20),),
             );
           }
           else if(idx == 5){
             return Container(
               margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
               child: TextField(
                 controller: memoController,
                 minLines: 10,
                 maxLines: 20,
                 decoration: InputDecoration(
                   border: OutlineInputBorder(
                     borderSide: BorderSide(color: Colors.black)
                   )
                 ),
               ),
             );
           }
           return Container();
      },
        itemCount: 6,
      ),
    );
  }
}

 

# 실행 결과

 

4. 화면 투명하게 꾸미기 

#main.dart

import 'package:diary/write.dart';
import 'package:flutter/material.dart';

import 'data/database.dart';
import 'data/diary.dart';
import 'data/util.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  int selectIndex = 0;
  final dbHelper = DatabaseHelper.instance;
  Diary todayDiary;

  List<String> statusImg = [
    "assets/img/icoweather.png",
    "assets/img/icoweather_2.png",
    "assets/img/icoweather_3.png",
  ];


  void getTodayDiary() async{
    List<Diary> diary = await dbHelper.getDiaryByDate(Utils.getFormatTime(DateTime.now()));
    if(diary.isNotEmpty){
      todayDiary = diary.first;
    }
    setState((){

    });
  }

  @override
  void initSate(){
    super.initState();
    getTodayDiary();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(""),
      ),
      body: Container(child: getPage()),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
            Diary _d;
            if(todayDiary != null){
              _d = todayDiary;
            }else{
              _d = Diary(
                  date: Utils.getFormatTime(DateTime.now()),
                  title: "",
                  memo: "",
                  status: 0,
                  image: "assets/img/b1.jpg"
              );
            }
            await Navigator.of(context).push(MaterialPageRoute(builder: (ctx) => DiaryWritePage(
              diary: _d,
            )));
            getTodayDiary();
        },
      tooltip: "Increment",
      child:Icon(Icons.add),
    ),
    bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
              icon: Icon(Icons.today),
              label: "오늘"
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.calendar_today_rounded),
              label: "기록"
          ),
          BottomNavigationBarItem(
              icon: Icon(Icons.insert_chart),
              label: "통계"
          ),
        ],
        onTap: (idx){
          setState(() {
            selectIndex = idx;
          });
        },
      ),
    );
  }

  Widget getPage(){
    if (selectIndex == 0){
      return getTodayPage();
    }else if(selectIndex == 1){
      return getHistoryPage();
    }else{
      getChartPage();
    }
  }

  Widget getTodayPage(){
    if(todayDiary == null){
      return Container(
        child: Text("일기 작성 해주세요~~~"),
      );
    }

    print(todayDiary.title);
    return Container(
      child: Stack(
        children: [
          Positioned.fill(
              child: Image.asset(todayDiary.image, fit: BoxFit.cover,),
          ),
          Positioned.fill(
            child: ListView(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text("${DateTime.now().month}.${DateTime.now().day}",
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold,
                    color: Colors.white),),
                    Image.asset(statusImg[todayDiary.status], fit: BoxFit.contain,)
                  ],
                ),
                Container(
                  decoration: BoxDecoration(
                    color: Colors.white54
                  ),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(todayDiary.title, style: TextStyle(fontSize: 18),),
                      Container(height: 12,),
                      Text(todayDiary.memo, style: TextStyle(fontSize: 18),)
                    ]
                  )
                )
              ],
            )
          )
        ],
      ),
    );
  }

  Widget getHistoryPage(){
    return Container();
  }

  Widget getChartPage(){
    return Container();
  }
}

# main.dart 

Container(
    margin: EdgeInsets.symmetric(vertical: 16, horizontal: 20),
    padding: EdgeInsets.symmetric(vertical: 16, horizontal: 20),
    decoration: BoxDecoration(
        color: Colors.white54,
        borderRadius: BorderRadius.circular(16)
  ),
  child: Column(
    mainAxisAlignment: MainAxisAlignment.start,
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(todayDiary.title, style: TextStyle(fontSize: 18),),
      Container(height: 12,),
      Text(todayDiary.memo, style: TextStyle(fontSize: 18),)
    ]
  )
)


# 실행 결과

 

5. AppBar 지우기 

# main.dart

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Container(child: getPage()),
    floatingActionButton: FloatingActionButton(
      onPressed: () async {
          Diary _d;
          if(todayDiary != null){
            _d = todayDiary;
          }else{
            _d = Diary(
                date: Utils.getFormatTime(DateTime.now()),
                title: "",
                memo: "",
                status: 0,
                image: "assets/img/b1.jpg"
            );
          }

# 실행 결과
상단 앱 표시줄 제거 됨

# 교육 소감

오늘은 실제 일기 작성 앱에서 가장 중요한 일기를 작성하는 페이지를 구현 하였다. 지난번 시간에 이미지를 추가하는데 많은 오류를 뿜어내서 오늘 강의는 수월하게 진행했으면 하는 마음이 있었다. getTodayPage를 만들면서 경우의 수를 생각해야하는데, 아직 일기를 작성하지 않았을 때와 일기를 이미 작성했을 때를 나누어서, 일기를 작성하지 않았을 때를 null 이면(비어있다면), 일기를 작성해달라고 요청하고, 이미 일기를 작성했을 때는 어떻게 본문에 출력하는지 화면을 구성하였다. 화면을 구성하기 위해서는 Stack widget 을 사용하여, 플로터 구조상 바탕화면 위에 이미지를 띄우는 구조로 구현을 해야 한다. 그릴 스택에 전체를 채우기 위해 Posioned.fill 를 통해 fit: BoxFit.cover 를 채워야하고, Positioned.fill 은 ListView 를 통해 Row에 오늘의 날짜를 입력하게 구현하였다. 또한 일기 내용을 적절하게 배치하고 배경을 이쁘게 하기 위해 BoxDecoration 을 추가하여 Colors.white54 의 색감을 추가 하였다. 이로써 일기를 작성하면 메인 화면에 일기 내용이 어떻게 보여지게 하는지 학습할 수 있었다. 

# 본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.

https://bit.ly/3FVdhDa

 

수강료 100% 환급 챌린지 | 패스트캠퍼스

딱 5일간 진행되는 환급챌린지로 수강료 100% 환급받으세요! 더 늦기전에 자기계발 막차 탑승!

fastcampus.co.kr

 

반응형

댓글