파이어스토어+ api+ 리버팟 mvvm 구조

Peter SHIN·2025년 4월 26일
lib/
 ├── models/            # 데이터 모델 정의하는 폴더 (예: Post라는 데이터 구조를 정의)
 │    └── post.dart     # Post 클래스: id, title 같은 데이터를 담는 객체
 │
 ├── repositories/      # 데이터 소스와 통신하는 역할 (DB, API 등)
 │    ├── firestore_repository.dart  # Firestore(DB)와 통신하는 코드 (읽고 쓰기)
 │    └── api_repository.dart        # 외부 REST API 서버랑 통신하는 코드 (HTTP 요청)
 │
 ├── viewmodels/        # 앱의 비즈니스 로직, 상태 관리를 담당 (MVVM에서 ViewModel 역할)
 │    └── post_viewmodel.dart  # Firestore, API에서 가져온 데이터를 화면에 전달할 준비를 하는 로직
 │
 ├── pages/             # 화면(UI) 관련 코드 모아놓은 폴더
 │    └── post_page.dart   # 화면에 실제 리스트 뿌려주는 UI 페이지
 │
 └── main.dart          # 앱 시작점. ProviderScope(상태관리) 세팅하고 첫 화면(PostPage) 보여주는 코드

models/post.dart

class Post {
  final String id;
  final String title;

  Post({required this.id, required this.title});

  factory Post.fromJson(Map<String, dynamic> json) {
    return Post(
      id: json['id'],
      title: json['title'],
    );
  }
}

repositories/firestore_repository.dart

import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/post.dart';

class FirestoreRepository {
  final _db = FirebaseFirestore.instance;

  Future<List<Post>> fetchPosts() async {
    final snapshot = await _db.collection('posts').get();
    return snapshot.docs.map((doc) => Post(
      id: doc.id,
      title: doc['title'],
    )).toList();
  }
}

repositories/api_repository.dart

import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/post.dart';

class ApiRepository {
  final String apiUrl = 'https://jsonplaceholder.typicode.com/posts';

  Future<List<Post>> fetchPosts() async {
    final response = await http.get(Uri.parse(apiUrl));
    final List<dynamic> data = jsonDecode(response.body);
    return data.map((json) => Post.fromJson(json)).toList();
  }
}

viewmodels/post_viewmodel.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/post.dart';
import '../repositories/firestore_repository.dart';
import '../repositories/api_repository.dart';

final postViewModelProvider = StateNotifierProvider<PostViewModel, AsyncValue<List<Post>>>(
  (ref) => PostViewModel(ref),
);

class PostViewModel extends StateNotifier<AsyncValue<List<Post>>> {
  PostViewModel(this.ref) : super(const AsyncLoading());

  final Ref ref;
  final _firestoreRepo = FirestoreRepository();
  final _apiRepo = ApiRepository();

  Future<void> fetchPostsFromFirestore() async {
    try {
      final posts = await _firestoreRepo.fetchPosts();
      state = AsyncData(posts);
    } catch (e, st) {
      state = AsyncError(e, st);
    }
  }

  Future<void> fetchPostsFromApi() async {
    try {
      final posts = await _apiRepo.fetchPosts();
      state = AsyncData(posts);
    } catch (e, st) {
      state = AsyncError(e, st);
    }
  }
}

pages/post_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../viewmodels/post_viewmodel.dart';

class PostPage extends ConsumerWidget {
  const PostPage({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final postsState = ref.watch(postViewModelProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Posts')),
      body: postsState.when(
        data: (posts) => ListView.builder(
          itemCount: posts.length,
          itemBuilder: (context, index) {
            final post = posts[index];
            return ListTile(
              title: Text(post.title),
            );
          },
        ),
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, stack) => Center(child: Text('Error: $error')),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            heroTag: 'firestore',
            onPressed: () => ref.read(postViewModelProvider.notifier).fetchPostsFromFirestore(),
            child: const Icon(Icons.cloud),
          ),
          const SizedBox(height: 16),
          FloatingActionButton(
            heroTag: 'api',
            onPressed: () => ref.read(postViewModelProvider.notifier).fetchPostsFromApi(),
            child: const Icon(Icons.api),
          ),
        ],
      ),
    );
  }
}

main.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'pages/post_page.dart';

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: PostPage(),
    );
  }
}

사용자 → 페이지(UI) → ViewModel(상태관리) → Repository(API/DB 통신) → 데이터(Model)

의미
M (Model) | 데이터 구조, 비즈니스 로직, 실제 데이터 (예: Post 모델, API 응답 데이터)
V (View) | 사용자한테 보여지는 화면(UI) (예: Flutter 페이지, 버튼, 텍스트)
VM (ViewModel) | View와 Model을 연결해주는 중간 관리자 (상태 관리, 로직 처리)

profile
플러터,자바,c언어

0개의 댓글