# 강의 제목 : 누적다운로드 120만+ 1인 개발자와 함께하는 앱 개발 입문 Online
# 강의 목표 : 기초부터 운영까지 앱 개발의 전체 프로세스를 이해한다.
내 이름으로 된 앱을 최대 10개까지 만들어 출시할 수 있다.
앱 개발자로 성장할 수 있는 초석을 다진다.
반응 얻는 앱의 특징과 노하우를 알아간다.
향후 강의 없이도 나만의 앱을 개발할수 있는 실력을 가진다.
# 강의 요약 : 프로그램 설치부터 기본 문법, 광고 다는 법, 클론코딩을 진행하며 필수 지식을 학습한다.
총 10개의 다른 주제로 실제 사용화 가능한 수준의 앱을 만들어본다.
나의 앱을 세상에 선보이기 위한 개발자 등록 및 배포를 진행한다.
강사님의 리뷰/클레임 대응사례 등 앱 성공 포인트를 참고해 1인 개발자로서의 입지를 다진다.
# 강의 목차 : Flutter 실전 앱 제작
- 앱 기능 및 디자인 설계 및 초기 구조 만들기
- TODO 화면 만들기
- TODO 화면 만들기
- TODO 화면 만들기
- TODO 기록 화면 만들기 (내부 DB 저장, 수정, 삭제)
- TODO 기록 화면 만들기 (이전 할일 기록 리스트, 카테고리 별 리스트) (12일차)
# 강의 화면 :
# 강의 내용 : Flutter 실전 앱 제작 (TODO 기록 화면 만들기 ((이전 할일 기록 리스트, 카테고리 별 리스트))
1. main.dart
import 'package:flutter/material.dart';
import 'data/database.dart';
import 'data/todo.dart';
import 'data/util.dart';
import 'write.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: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final dbHelper = DatabaseHelper.instance;
int selectIndex = 0;
List<Todo> todos = [
// Todo(
// title: "온라인교육듣기1",
// memo: "앱개발 강의 듣기",
// color: Colors.redAccent.value,
// done: 0,
// category: "공부",
// date: 20210709
// ),
// Todo(
// title: "온라인교육듣기2",
// memo: "앱개발 강의 듣기",
// color: Colors.blue.value,
// done: 1,
// category: "공부",
// date: 20210709
// ),
];
void getTodayTodo() async {
todos = await dbHelper.getTodoByDate(Utils.getFormatTime(DateTime.now()));
setState(() {});
}
void getAllTodo() async {
allTodo = await dbHelper.getAllTodo();
setState(() {});
}
@override
void initState() {
getTodayTodo();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(child:AppBar(),
preferredSize: Size.fromHeight(0),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add, color: Colors.white,),
onPressed: () async {
// 화면 이동을 해야합니다.
Todo todo = await Navigator.of(context).push(
MaterialPageRoute(builder: (ctx) => TodoWritePage(todo: Todo(
title: "",
color: 0,
memo: "",
done: 0,
category: "",
date: Utils.getFormatTime(DateTime.now().subtract(Duration(days: 1)))
))));
getTodayTodo();
},
),
body: getPage(),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.today_outlined),
label: "오늘"
),
BottomNavigationBarItem(
icon: Icon(Icons.assignment_outlined),
label: "기록"
),
BottomNavigationBarItem(
icon: Icon(Icons.more_horiz),
label: "더보기"
),
],
currentIndex: selectIndex,
onTap: (idx){
if(idx == 1){
getAllTodo();
}
setState(() {
selectIndex = idx;
});
},
),
);
}
Widget getPage(){
if(selectIndex == 0){
return getMain();
}
else {
return getHistory();
}
}
Widget getMain(){
return ListView.builder(
itemBuilder: (ctx, idx){
if(idx == 0){
return Container(
child: Text("오늘하루", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),),
margin: EdgeInsets.symmetric(vertical: 12, horizontal: 20),
);
}else if(idx == 1){
List<Todo> undone = todos.where((t){
return t.done == 0;
}).toList();
return Container(
child: Column(
children: List.generate(undone.length, (_idx){
Todo t = undone[_idx];
return InkWell(child: TodoCardWidget(t: t),
onTap: () async {
setState(() {
if(t.done == 0){
t.done = 1;
}else{
t.done = 0;
}
});
await dbHelper.insertTodo(t);
},
onLongPress: () async {
getTodayTodo();
},
);
}),
),
);
}
else if(idx == 2){
return Container(
child: Text("완료된 하루", style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),),
margin: EdgeInsets.symmetric(vertical: 12, horizontal: 20),
);
}else if(idx == 3){
List<Todo> done = todos.where((t){
return t.done == 1;
}).toList();
return Container(
child: Column(
children: List.generate(done.length, (_idx){
Todo t = done[_idx];
return InkWell(child: TodoCardWidget(t: t),
onTap: () async {
setState(() {
if(t.done == 0){
t.done = 1;
}else{
t.done = 0;
}
});
await dbHelper.insertTodo(t);
},
onLongPress: () async {
Todo todo = await Navigator.of(context).push(
MaterialPageRoute(builder: (ctx) => TodoWritePage(todo: t)));
setState(() {});
},
);
}),
),
);
}
return Container();
},
itemCount: 4,
);
}
List<Todo> allTodo = [];
Widget getHistory(){
return ListView.builder(
itemBuilder: (ctx, idx){
return TodoCardWidget(t: allTodo[idx]);
},
itemCount: allTodo.length,
);
}
}
class TodoCardWidget extends StatelessWidget {
final Todo t;
TodoCardWidget({Key key, this.t}) :super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: Color(t.color),
borderRadius: BorderRadius.circular(16)
),
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 12),
margin: EdgeInsets.symmetric(vertical: 8, horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(t.title, style: TextStyle(fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold),),
Text(t.done == 0 ? "미완료" : "완료",
style: TextStyle(color: Colors.white),)
],
),
Container(height: 8),
Text(t.memo, style: TextStyle(color: Colors.white))
],
),
);
}
}
2. write.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:todo/data/database.dart';
import 'data/todo.dart';
class TodoWritePage extends StatefulWidget {
final Todo todo;
TodoWritePage({Key key, this.todo}): super(key: key);
@override
State<StatefulWidget> createState() {
return _TodoWritePageState();
}
}
class _TodoWritePageState extends State<TodoWritePage> {
TextEditingController nameController = TextEditingController();
TextEditingController memoController = TextEditingController();
final dbHelper = DatabaseHelper.instance;
int colorIndex = 0;
int ctIndex = 0;
@override
void initState() {
super.initState();
nameController.text = widget.todo.title;
memoController.text = widget.todo.memo;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
TextButton(
child: Text("저장", style: TextStyle(color: Colors.white),),
onPressed: () async {
// 페이지 저장시 사용
widget.todo.title = nameController.text;
widget.todo.memo = memoController.text;
await dbHelper.insertTodo(widget.todo);
Navigator.of(context).pop(widget.todo);
},
)
],
),
body: ListView.builder(
itemBuilder: (ctx, idx){
if(idx == 0){
return Container(
child: Text("제목", style: TextStyle(fontSize: 20),),
margin: EdgeInsets.symmetric(vertical: 12, horizontal: 16),
);
}else if(idx == 1){
return Container(
child: TextField(
controller: nameController,
),
margin: EdgeInsets.symmetric(horizontal: 16),
);
}
else if(idx == 2){
return InkWell(child: Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("색상", style: TextStyle(fontSize: 20),),
Container(
width: 20,
height: 20,
color: Color(widget.todo.color),
)
],
),
),
onTap: (){
List<Color> colors = [
Color(0xFF80d3f4),
Color(0xFFa794fa),
Color(0xFFfb91d1),
Color(0xFFfb8a94),
Color(0xFFfebd9a),
Color(0xFF51e29d),
Color(0xFFFFFFFF),
];
widget.todo.color = colors[colorIndex].value;
colorIndex++;
setState(() {
colorIndex = colorIndex % colors.length;
});
},
);
}
else if(idx == 3){
return InkWell(child: Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("카테고리", style: TextStyle(fontSize: 20),),
Text(widget.todo.category)
],
),
),
onTap: (){
List<String> category = ["공부", "운동", "게임"];
widget.todo.category = category[ctIndex];
ctIndex++;
setState(() {
ctIndex = ctIndex % category.length;
});
},
);
}else if(idx == 4){
return Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Text("메모", style: TextStyle(fontSize: 20),),
);
}else if(idx == 5){
return Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 1),
child: TextField(
controller: memoController,
maxLines: 10,
minLines: 10,
decoration: InputDecoration(
border: OutlineInputBorder(borderSide: BorderSide(color: Colors.black))
),
),
);
}
return Container();
},
itemCount: 6,
),
);
}
}
3. database.dart
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:todo/data/todo.dart';
class DatabaseHelper {
static final _databaseName = "todo.db";
static final _databaseVersion = 1;
static final todoTable = "todo";
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
static Database _database;
Future<Database> get database async {
if(_database != null) return _database;
_database = await _initDatabase();
return _database;
}
_initDatabase() async {
var databasePath = await getDatabasesPath();
String path = join(databasePath, _databaseName);
return await openDatabase(path, version: _databaseVersion, onCreate: _onCreate,
onUpgrade: _onUpgrade);
}
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE IF NOT EXISTS $todoTable (
id INTEGER PRIMARY KEY AUTOINCREMENT,
date INTEGER DEFAULT 0,
done INTEGER DEFAULT 0,
title String,
memo String,
color INTEGER,
category String
)
''');
}
Future _onUpgrade(Database db, int oldVersion, int newVersion) async {}
// 투두 입력, 수정, 불러오기
Future<int> insertTodo(Todo todo) async {
Database db = await instance.database;
if(todo.id == null){
// 새로 추가
Map<String, dynamic> row = {
"title": todo.title,
"date": todo.date,
"done": todo.done,
"memo": todo.memo,
"color": todo.color,
"category": todo.category
};
return await db.insert(todoTable, row);
}else{
Map<String, dynamic> row = {
"title": todo.title,
"date": todo.date,
"done": todo.done,
"memo": todo.memo,
"color": todo.color,
"category": todo.category
};
return await db.update(todoTable, row, where: "id = ?", whereArgs: [todo.id]);
}
}
Future<List<Todo>> getAllTodo() async {
Database db = await instance.database;
List<Todo> todos = [];
var queries = await db.query(todoTable);
print(queries);
for(var q in queries){
todos.add(Todo(
id: q["id"],
title: q["title"],
date: q["date"],
done: q["done"],
memo: q["memo"],
category: q["category"],
color: q["color"],
));
}
return todos;
}
Future<List<Todo>> getTodoByDate(int date) async {
Database db = await instance.database;
List<Todo> todos = [];
var queries = await db.query(todoTable, where: "date = ?", whereArgs: [date]);
for(var q in queries){
todos.add(Todo(
id: q["id"],
title: q["title"],
date: q["date"],
done: q["done"],
memo: q["memo"],
category: q["category"],
color: q["color"],
));
}
return todos;
}
}
4. 실행결과
# 교육 소감
저번 강의 때 내부 앱의 DB를 설치 하였다면, 이번 강의에서는 실제 데이터베이스를 사용하여, 추가하고 업데이트하고 기록을 불러오는 기록페이지를 만들었다. 추가할수 있는 insert todo 와 조회 할수 있는 get todo 를 database.dart에 만들었고, 실제 main.dart 에서 이 DB를 사용하기 위해 다양한 소스를 작성하고 학습하였다. void getTodayTodo 에서는 오늘 날짜 기준에 있는 todo 들을 가져올수 있도록 만들었으며, 가져올때는 async 를 해야하며 실제로 화면이 갱신되도록 구현해야한다. 실제 실행은 앱이 처음 실행될때 이 함수가 호출되도록 해야한다. 또한 todo 작성 페이지에서 실제 저장 버튼을 누를 때, dbhelper 인스턴스를 통해서 기록되게 해야하는데 기록할 때 시간이 걸리기 때문에 await 를 사용하여 비동기적으로 실행될수 있도록 기다림을 줄수 있도록 한다. 일단 데이터가 추가 되면 getTodayTodo 를 호출하여 저장된 부분들이 로딩되도록 구현 해야 한다. 이와 같이 DB 를 사용하여 저장과 조회가 용이하게 만들수 있으며 이를 통해 기록 페이지에서 실제 작업된 내용들이 모두 출력될 수 있도록 조회 될수 있게 만들수 있다. 오늘 까지 해서 기본적으로 dart를 통해서 화면을 변경시키고 내부 DB를 통해서 static 하게 값을 사용하지 않고 동적으로 데이터를 넣고 조회 가능하도록 todo 앱을 만들수 있었다. 아직도 어려운 부분이 많지만, 계속 반복해보면서 익숙해지도록 노력해야겠다.
# 본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성되었습니다.
'코딩 알로하 :: two > 하이브리드앱' 카테고리의 다른 글
패스트캠퍼스 챌린지 14일차 (0) | 2021.11.14 |
---|---|
패스트캠퍼스 챌린지 13일차 (0) | 2021.11.13 |
패스트캠퍼스 챌린지 11일차 (0) | 2021.11.11 |
패스트캠퍼스 챌린지 10일차 (0) | 2021.11.11 |
패스트캠퍼스 챌린지 9일차 (0) | 2021.11.09 |
댓글