본문 바로가기

Flutter/12 Clone 'Used Goods app'

[Flutter] Clone - 당근마켓35(ItemModel upload)

이번에는 "거래 등록" 화면에 작성한 글/이미지를 Firebase 에 업로드하는것을 구현해보겠습니다.

개발환경 : 윈도우11, 안드로이드 스튜디오, flutter 3.0.1

 

 

 

구현 화면은 아래와 같다

 

 

 

 

./src/repo/item_service.dart - 새로운 글을 저장하는 로직 구현

 

import 'package:cloud_firestore/cloud_firestore.dart';

import '../constants/data_keys.dart';
import '../models/item_model.dart';
import '../utils/logger.dart';

  Future createNewItem(ItemModel2 itemModel, String itemKey, String userKey) async {
    // 신규 작성글을 업로드하기 위한 위치 설정
    DocumentReference<Map<String, dynamic>> itemDocRef =
        FirebaseFirestore.instance.collection(COL_ITEMS).doc(itemKey);
    final DocumentSnapshot documentSnapshot = await itemDocRef.get();

    // 신규 작성글을 사용자 정보의 하위 콜랙션에 추가, No SQL 에서는 역정규화 하는것이 좋을때가 있음,
    DocumentReference<Map<String, dynamic>> userItemDocRef = FirebaseFirestore
        .instance
        .collection(COL_USERS)
        .doc(userKey)
        .collection(COL_USER_ITEMS)
        .doc(itemKey);

    // 신규 작성글이 없다면 저장,
    if (!documentSnapshot.exists) {
      // await itemDocRef.set(itemModel.toJson());
      await FirebaseFirestore.instance.runTransaction((transaction) async {
        // 2개중에 하나라도 오류가 발생하면 모두 롤백한다,
        transaction.set(itemDocRef, itemModel.toJson());
        transaction.set(userItemDocRef, itemModel.toMinJson());
      });
    }
  }
}

 

 

 

./src/screens/input/input_screen.dart - 입력할 항목을 확인하고 미입력시 알림창으로 입력을 유도한다.

 

void attemptCreateItem() async {
  if (FirebaseAuth.instance.currentUser == null) return;
  // 완료 버튼 클릭
  isCreatingItem = true;
  // setState 해줘야 인디케이터가 동작한다,
  setState(() {});

  final String userKey = FirebaseAuth.instance.currentUser!.uid;
  final String userPhone = FirebaseAuth.instance.currentUser!.phoneNumber!;
  final String itemKey = ItemModel2.generateItemKey(userKey);
  List<Uint8List> images = SelectImageController.to.images;
  // final num? price = num.tryParse(_priceController.text.replaceAll('.', '').replaceAll(' 원', ''));
  final num? price = num.tryParse(_priceController.text.replaceAll(RegExp(r'\D'), ''));
  // UserNotifier userNotifier = context.read<UserNotifier>();

  if (images.isEmpty) {
    dataWarning(context, '확인', '이미지를 선택해주세요');
    return;
  }

  if (_titleController.text.isEmpty) {
    dataWarning(context, '확인', '제목을 입력해주세요');
    return;
  }

  if (CategoryController.to.currentCategoryInEng == 'none') {
    dataWarning(context, '확인', '카테고리를 선택해주세요');
    return;
  }

  if (price == null) {
    dataWarning(context, '확인', '가격을 입력해주세요');
    return;
  }

  if (_detailController.text.isEmpty) {
    dataWarning(context, '확인', '내용을 입력해주세요');
    return;
  }

  // uploading raw data and return the Urls,
  List<String> downloadUrls = await ImageStorage.uploadImage(images, itemKey);
  logger.d('upload finished(${downloadUrls.length}) : $downloadUrls');

  ItemModel2 itemModel = ItemModel2(
    itemKey: itemKey,
    userKey: userKey,
    userPhone: userPhone,
    imageDownloadUrls: downloadUrls,
    title: _titleController.text,
    category: CategoryController.to.currentCategoryInEng,
    price: price,
    // price ?? 0
    negotiable: _suggestPriceSelected,
    detail: _detailController.text,
    address: UserController.to.userModel.value!.address,
    //userNotifier.userModel!.address,
    geoFirePoint: UserController.to.userModel.value!.geoFirePoint,
    //userNotifier.userModel!.geoFirePoint,
    createdDate: DateTime.now().toUtc(),
  );

  // await ItemService().createNewItem(itemModel, itemKey, userNotifier.user!.uid);
  await ItemService().createNewItem(itemModel, itemKey, UserController.to.user.value!.uid);
  Get.back();
}

Future<bool> dataWarning(BuildContext context, String title, String msg) async {
  isCreatingItem = false;
  return await showDialog<bool>(
        context: context,
        builder: (context) => WarningYesNo(
          title: title,
          msg: msg,
          yesMsg: '확인',
        ),
      ) ??
      false;
}

 

 

 

./src/widgets/warning_dialog.dart

 

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class WarningYesNo extends StatelessWidget {
  final String title;
  final String msg;
  final String yesMsg;

  const WarningYesNo({Key? key, required this.title, required this.msg, required this.yesMsg})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text(
        title,
        textAlign: TextAlign.center,
      ),
      content: Text(
        msg,
      ),
      actions: <Widget>[
        Container(
          decoration: const BoxDecoration(),
          child: MaterialButton(
            onPressed: () {
              debugPrint('WarningYesNo >> true');
              Get.back(result: true);
            },
            child: Text(yesMsg),
          ),
        ),
      ],
    );
  }
}

 

 

 

저장된 결과는 아래와 같습니다. 사용자 콜렉션 하위에 저장된 요약정보와 아이템 콜렉션 하위에 저장된 상세정보