# 강의 제목 : 누적다운로드 120만+ 1인 개발자와 함께하는 앱 개발 입문 Online
# 강의 목표 : 기초부터 운영까지 앱 개발의 전체 프로세스를 이해한다.
내 이름으로 된 앱을 최대 10개까지 만들어 출시할 수 있다.
앱 개발자로 성장할 수 있는 초석을 다진다.
반응 얻는 앱의 특징과 노하우를 알아간다.
향후 강의 없이도 나만의 앱을 개발할수 있는 실력을 가진다.
# 강의 요약 : 프로그램 설치부터 기본 문법, 광고 다는 법, 클론코딩을 진행하며 필수 지식을 학습한다.
총 10개의 다른 주제로 실제 사용화 가능한 수준의 앱을 만들어본다.
나의 앱을 세상에 선보이기 위한 개발자 등록 및 배포를 진행한다.
강사님의 리뷰/클레임 대응사례 등 앱 성공 포인트를 참고해 1인 개발자로서의 입지를 다진다.
# 강의 목차 : Flutter 실전 앱 제작
- 앱 기능 및 디자인 설계 및 초기 구조 만들기 (일기앱)
- 일기 작성 화면 만들기 (14일차)
- 일기 작성 화면 만들기 (이모티콘 추가)
- 달력 화면 만들기
- 통계, 더보기 화면 만들기
- Splash 스크린 추가하기
# 강의 화면 :
# 강의 내용 : Flutter 실전 앱 제작 (일기 작성 화면 만들기)
1. 에셋(Assets)
어플리케이션 개발 내에서 사용하는 이미지, 영상, 폰트 등 필요한 요소
- 사용 방법
- pubspec.yaml 파일 내 asset: 항목에 필요한 파일 이름 입력
- 특정 파일을 추가할 경우
- assets/img/ico-weather.png
- 폴더 내의 전체 에셋을 추가할 경우
- assets/img/
img 폴더 내 모든 에셋을 추가
# pubspec.yaml (주의: 에셋 위치 설정 시 띄어쓰기, 탭 등 주의)
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- assets/img/b1.jpg
- assets/img/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# wirte.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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;
@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
actions: [
TextButton(child: Text("저장", style: TextStyle(color: Colors.white),),
onPressed: (){
})
],
),
body: ListView.builder(
itemBuilder: (ctx, idx){
if(idx == 0){
return InkWell(child: Container(
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(
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(12),
decoration: BoxDecoration(
border: Border.all(color: _idx == widget.diary.status ? Colors.blue : Colors.transparent,),
borderRadius: BorderRadius.circular(100)
),
), onTap: () {
setState(() {
widget.diary.status = _idx;
});
},);
}),
),
);
}
return Container();
},
itemCount: 6,
),
);
}
}
# 실행 결과
2. 제목 과 내용 라인 그리기
# write.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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();
@override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
actions: [
TextButton(child: Text("저장", style: TextStyle(color: Colors.white),),
onPressed: (){
})
],
),
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(
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: nameController,
minLines: 10,
maxLines: 20,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black)
)
),
),
);
}
return Container();
},
itemCount: 6,
),
);
}
}
# 실행 결과
3. 저장 버튼 및 DB 저장 구현하기
# 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
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
actions: [
TextButton(child: Text("저장", style: TextStyle(color: Colors.white),),
onPressed: () async {
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: nameController,
minLines: 10,
maxLines: 20,
decoration: InputDecoration(
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.black)
)
),
),
);
}
return Container();
},
itemCount: 6,
),
);
}
}
4. 수정하기
# wirte.dart
@override
void initState(){
super.initState();
nameController.text = widget.diary.title;
memoController.text = widget.diary.memo;
}
# 교육 소감
오늘은 일기앱 개발에 있어서 일기 작성 페이지에 대해서 구현하였다. 일기 작성 할때 날씨를 선택하여 띄우기 위해 처음으로 이미지를 삽입하여 앱에 출력하게 구현을 하였다. 그런데, 에셋 폴더를 만들고 그안에 이미지를 넣고, 안드로이드 IDE에서 Dart에 라이브러리 설정을 하는 pubspec.yaml 파일에 에셋 지정을 하는 부분에서 오류가 발생하였다. Asset 라이브러리가 주석 되어있는데, 주석을 풀고, 이미지 위치를 지정하고 pub get 하는 순간 오류가 ^^;;;; 구글링을 통해 확인하니 탭, 띄어쓰기, 공백에 상당한 주의가 필요한것을 확인했다. 띄어쓰기에 주의하여 작성했지만, 계속 오류,,,,,, 이미지 파일 이름에 공백이 들어가거나 특수기호 등 문제가 발생한다 하여 이미지 파일명도 바꾸었다. 이미지 경로 설정하는데 이렇게 예민할줄이야... 이부분은 구글에서 수정이 필요한 것 같다. 어찌됐든 구글신의 도움을 받아 성공했지만,,, Dart 언어에 대해서 아직 기초 수준이라, 이미 오류를 경험하고 해결책을 찾은 분이 없었더라면, 꽤 오랜시간 동안 ..헤맸을것 같다. 이미지를 띄우고, 이미지를 이쁘게 띄우기 위한 여러 설정값들을 넣으면서 점차 일기 쓰기 페이지를 완성시켰고, 실제로 저장 버튼을 울러 DB 에 저장 시키는 부분까지 구현 하였다.
# 본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
'코딩 알로하 :: two > 하이브리드앱' 카테고리의 다른 글
패스트캠퍼스 챌린지 16일차 (0) | 2021.11.16 |
---|---|
패스트캠퍼스 챌린지 15일차 (0) | 2021.11.15 |
패스트캠퍼스 챌린지 13일차 (0) | 2021.11.13 |
패스트캠퍼스 챌린지 12일차 (0) | 2021.11.12 |
패스트캠퍼스 챌린지 11일차 (0) | 2021.11.11 |
댓글