
* 겪고 있는 문제 상황을 최대한 자세하게 작성해주세요.
* 문제 해결을 위해 어떤 시도를 해보았는지 구체적으로 함께 알려주세요.
과제 요구조건은 아래 사진과 같습니다.

여기서 저는 아래와 같이 비슷하지만 조금 다르게 만들기로 했습니다.
1. 저장된 메모를 좌측으로 슬라이드 했을 때 삭제 또는 상단 고정할 수 있는 기능을 보여주고 그 기능을 클릭했을 때 작동할 수 있도록 하며, 핀은 붉은 색으로
2. 메모를 생성 / 작성한 시간을 제목 우측 하단에 노출
3. 내용이 없는 메모는 자동 삭제 하는 기능 추가
현재 결론은
⭕️ 좌우측 슬라이딩 가능
⭕️ 핀 고정 시 붉은색
⭕️ 날짜 하단 설정 완료
🔺 현재 슬라이딩으로 핀 고정했을 시 오류 발생하며 새로고침을 해야 반영됨

❌ 빈 내용 메모 삭제 안됨
-------------------------
이때 발생하는 문제는 State Management 관련해 화면에 정보 변경 상태들이 즉각적으로 반영이 안되고 있는 것 같습니다.
이 문제의 원인과 해결 방법을 위해서 많은 시도를 해 보았지만... 해결이 되지 않아서 문의 드립니다.
(만약 안된다면... 코드가 정상적으로 작동하지 않는다고 해도, 어떤 부분의 문제이고, 어떤 방법으로 해결을 해야 문제가 풀릴지 라도 알 수 있었으면 합니다...)
-------------------------

main.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'memo_service.dart';
late SharedPreferences prefs;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
prefs = await SharedPreferences.getInstance();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => MemoService()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Consumer<MemoService>(
builder: (context, memoService, child) {
List<Memo> memoList = memoService.memoList;
return Scaffold(
appBar: AppBar(
title: Text("mymemo"),
),
body: memoList.isEmpty
? Center(child: Text("메모를 작성해 주세요"))
: ListView.builder(
itemCount: memoList.length,
itemBuilder: (context, index) {
Memo memo = memoList[index];
return Dismissible(
key: Key(memo.id),
background: Container(
color: Colors.red,
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.delete, color: Colors.white),
),
),
),
secondaryBackground: Container(
color: Colors.blue,
child: Align(
alignment: Alignment.centerRight,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Icon(Icons.pin, color: Colors.white),
),
),
),
onDismissed: (direction) {
setState(() {
if (direction == DismissDirection.startToEnd) {
memoService.deleteMemo(index: index);
} else if (direction == DismissDirection.endToStart) {
memoService.togglePin(index: index);
}
});
},
child: ListTile(
leading: Icon(
memo.isPinned
? CupertinoIcons.pin_fill
: CupertinoIcons.pin,
color: memo.isPinned ? Colors.red : null,
),
title: Text(
memo.content,
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
subtitle: Text(
memo.createdAt.toLocal().toString().split('.')[0],
style: TextStyle(fontSize: 12),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DetailPage(
index: index,
),
),
);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
memoService.createMemo(content: '');
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => DetailPage(
index: memoService.memoList.length - 1,
),
),
);
},
),
);
},
);
}
}
class DetailPage extends StatelessWidget {
DetailPage({super.key, required this.index});
final int index;
TextEditingController contentController = TextEditingController();
@override
Widget build(BuildContext context) {
MemoService memoService = context.read<MemoService>();
Memo memo = memoService.memoList[index];
contentController.text = memo.content;
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () {
showDeleteDialog(context, memoService);
},
icon: Icon(Icons.delete),
)
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: TextField(
controller: contentController,
decoration: InputDecoration(
hintText: "메모를 입력하세요",
border: InputBorder.none,
),
autofocus: true,
maxLines: null,
expands: true,
keyboardType: TextInputType.multiline,
onChanged: (value) {
memoService.updateMemo(index: index, content: value);
},
),
),
);
}
void showDeleteDialog(BuildContext context, MemoService memoService) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("정말로 삭제하시겠습니까?"),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("취소"),
),
TextButton(
onPressed: () {
memoService.deleteMemo(index: index);
Navigator.pop(context);
Navigator.pop(context);
},
child: Text(
"확인",
style: TextStyle(color: Colors.pink),
),
),
],
);
},
);
}
}
memo_service.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import 'main.dart';
class Memo {
String id;
String content;
DateTime createdAt;
bool isPinned;
Memo({
required this.id,
required this.content,
required this.createdAt,
this.isPinned = false,
});
factory Memo.fromJson(Map<String, dynamic> json) {
return Memo(
id: json['id'] as String? ?? Uuid().v4(), // null 안전 처리
content: json['content'] as String? ?? '', // null 안전 처리
createdAt: DateTime.tryParse(json['createdAt'] as String? ?? '') ??
DateTime.now(), // null 안전 처리
isPinned: json['isPinned'] as bool? ?? false, // null 안전 처리
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'content': content,
'createdAt': createdAt.toIso8601String(),
'isPinned': isPinned,
};
}
}
class MemoService extends ChangeNotifier {
List<Memo> _memoList = [];
List<Memo> get memoList => _memoList;
MemoService() {
loadMemoList();
}
void loadMemoList() {
String? jsonString = prefs.getString('memoList');
if (jsonString == null || jsonString.isEmpty) {
_memoList = [];
} else {
List memoJsonList = jsonDecode(jsonString) as List;
_memoList = memoJsonList.map((json) => Memo.fromJson(json)).toList();
}
notifyListeners();
}
void saveMemoList() {
List<Map<String, dynamic>> memoJsonList =
_memoList.map((memo) => memo.toJson()).toList();
String jsonString = jsonEncode(memoJsonList);
prefs.setString('memoList', jsonString);
}
void createMemo({required String content}) {
Memo memo =
Memo(id: Uuid().v4(), content: content, createdAt: DateTime.now());
_memoList.add(memo);
notifyListeners();
saveMemoList();
}
void updateMemo({required int index, required String content}) {
Memo memo = _memoList[index];
memo.content = content;
notifyListeners();
saveMemoList();
}
void deleteMemo({required int index}) {
_memoList.removeAt(index);
notifyListeners();
saveMemoList();
}
void togglePin({required int index}) {
Memo memo = _memoList[index];
memo.isPinned = !memo.isPinned;
notifyListeners();
saveMemoList();
}
}
추가로
build.gradle 버전도 변경했습니다.
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 33
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
applicationId "com.example.mymemo"
minSdkVersion 16
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
