커뮤니티
포인트
쿠폰
내 강의실
국비 신청 내역
증명서
계정
로그아웃
학습 질문
개발 일지
나의 활동
답변 완료
memo 앱 숙제 관련 질문 / 기능 구현 오류 문의... 😭😭😭
앱개발 종합반 - 플러터 v0
3주차
북마크
최*근
댓글
9
추천
0
조회수
11
조회수
11
답변 완료

* 겪고 있는 문제 상황을 최대한 자세하게 작성해주세요.

* 문제 해결을 위해 어떤 시도를 해보았는지 구체적으로 함께 알려주세요.


과제 요구조건은 아래 사진과 같습니다.

스파르타 즉문즉답


여기서 저는 아래와 같이 비슷하지만 조금 다르게 만들기로 했습니다.

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"
}




취소
 공유
취소
댓글 0
댓글 알림
나의얼굴