flutter mvvm패턴 공부

Gooak·2024년 2월 16일

지금 개인으로 만든 앱을 여자친구랑 함께 쓰고있다

여기서 mvvm 패턴을 적용한 예시를 적으려고 한다

View

  • 사용자에게 보여지는 View

ViewModel

  • View를 표현해주기 위한 Model이다.
  • View에게서 액션을 받고 View를 업데이트를 해줄 수 있다.

Model

  • 데이터를 가져오고 저장하는 역할을 한다.

Model.dart

class Event {
  String date;
  String weather;
  String second;
  String first;
  String secondEmail;
  String firstEmail;
  String secondMood;
  String firstMood;
  Timestamp? timestamp;
  Event({
    required this.date,
    required this.weather,
    required this.second,
    required this.first,
    required this.secondEmail,
    required this.firstEmail,
    required this.secondMood,
    required this.firstMood,
    required this.timestamp,
  });

  factory Event.fromDocumentSnapshot(DocumentSnapshot json) {
    return Event(
      date: json['Date'] == null ? '' : json['Date'] as String,
      weather: json['weather'] == null ? '' : json['weather'] as String,
      second: json['second'] == null ? '' : json['second'] as String,
      first: json['first'] == null ? '' : json['first'] as String,
      secondEmail: json['secondEmail'] == null ? '' : json['secondEmail'] as String,
      firstEmail: json['firstEmail'] == null ? '' : json['firstEmail'] as String,
      secondMood: json['secondMood'] == null ? '' : json['secondMood'] as String,
      firstMood: json['firstMood'] == null ? '' : json['firstMood'] as String,
      timestamp: json['timestamp'] == null ? null : json['timestamp'] as Timestamp,
    );
  }
  Map<String, dynamic> toJson() => {
        'Date': date,
        'weather': weather,
        'second': second,
        'first': first,
        'secondEmail': secondEmail,
        'firstEmail': firstEmail,
        'secondMood': secondMood,
        'firstMood': firstMood,
        'timestamp': timestamp,
      };
}

firebase를 활용하기 때문에 factory에 DocumentSnapsht 버전을 추가했다.

repository

  Future<List<Event>> getEventsAll(sort) async {
    List<Event> events = [];
    var data = await FirebaseFirestore.instance.collection('Calendar').orderBy('timestamp', descending: sort).get();

    for (var element in data.docs) {
      events.add(Event.fromDocumentSnapshot(element));
    }
    return events;
  }


repository에서 api호출을 해 데이터 모델로 저장한다

ViewModel.dart

class EventViewModel extends ChangeNotifier {
  final eventRepository = EventRepository();

  List<Event> _eventList = [];
  List<Event> get eventList => _eventList;

  Future<void> getEventAllList(bool sort) async {
    _eventList = await eventRepository.getEventsAll(sort);
    notifyListeners();
  }
}

이렇게 ViewModel에서 reposiotory에 api를 호출한다

View.dart

class CalendarListView extends StatefulWidget {
  const CalendarListView({super.key});

  @override
  State<CalendarListView> createState() => _CalendarListViewState();
}

class _CalendarListViewState extends State<CalendarListView> {
  bool sort = false;
  List<Event> eventList = [];
  @override
  Widget build(BuildContext context) {
    return Consumer<EventViewModel>(builder: (context, provider, child) { //provider Consumer로 ViewModel 활용
      provider.getEventAllList(sort); //api 호출
      eventList = provider.eventList; //연결
      return Scaffold(
        appBar: AppBar(
          leading: BackButton(
            color: Colors.black,
            onPressed: () {
              Navigator.pop(context);
            },
          ),
          title: const Text(
            '날씨 모아보기',
            style: TextStyle(fontSize: 16, color: Colors.black),
          ),
          actions: [
            IconButton(
              onPressed: () async {
                setState(() {
                  sort = !sort;
                });
                provider.getEventAllList(sort);
              },
              icon: Icon(sort == false ? Icons.arrow_downward : Icons.arrow_upward),
            ),
          ],
          elevation: 0,
        ),
        body: buildListView(context, eventList),
      );
    });
  }

  Widget buildListView(BuildContext context, List<Event> eventList) {
    return eventList.isEmpty
        ? const Center(
            child: Text('값이 없어요 ㅠㅠ'),
          )
        : ListView.builder(
            itemCount: eventList.length,
            itemBuilder: (context, index) {
              return ExpansionTile(
                title: Row(
                  children: [
                    Text(
                      eventList[index].date,
                      style: const TextStyle(
                        color: Color(0xFF7D5260),
                      ),
                    ),
                    const SizedBox(
                      width: 5,
                    ),
                    eventList[index].weather != ''
                        ? Image.asset(
                            'images/${eventList[index].weather}.png',
                            width: 43,
                            height: 43,
                          )
                        : const SizedBox.shrink()
                  ],
                ),
                children: [
                  Container(
                    margin: const EdgeInsets.fromLTRB(5, 15, 5, 15),
                    padding: const EdgeInsets.all(15),
                    decoration: BoxDecoration(
                      border: Border.all(
                        width: 2,
                        color: const Color(0xFFFFD8E4),
                      ),
                      borderRadius: BorderRadius.circular(12.0),
                    ),
                    width: MediaQuery.of(context).size.width,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(eventList[index].first,
                            style: eventList[index].firstEmail == Provider.of<UserProvider>(context, listen: false).email
                                ? const TextStyle(color: Color(0xFF6750A4))
                                : const TextStyle(color: Color(0xFF6750A4), fontWeight: FontWeight.bold)),
                        const SizedBox(
                          height: 20,
                        ),
                        eventList[index].second == ''
                            ? const SizedBox.shrink()
                            : Text(eventList[index].second,
                                style: eventList[index].secondEmail == Provider.of<UserProvider>(context, listen: false).email
                                    ? const TextStyle(color: Color(0xFF6750A4))
                                    : const TextStyle(color: Color(0xFF6750A4), fontWeight: FontWeight.bold)),
                      ],
                    ),
                  ),
                ],
              );
            },
          );
  }
}

이렇게 View -> ViewModel -> repository -> Model순으로 적용되었다
일단 repository는 단순 api를 호출하는 용도로 썼는데 다른 용도가 있을지는 잘 모르겠다..

상태관리로는 Provider를 써보았다
현업에서는 GetX를 쓰고있고 나중에 시간이 난다면 riverpod도 공부해봐야겠다

profile
구악

0개의 댓글