상세 컨텐츠

본문 제목

[Donfo] 1차 폭발

projects/Flutter

by 서울의볼 2024. 5. 15. 02:27

본문

처음부터 부족한 이해력으로 클린아키텍처를 적용하면서 개발하느라 애로사항이 많아 일단 기능개발에 초점을 맞추기로 함.

로그인 화면부터 다시 작업하게 됨.

 

- 일단 firestore rule부터 모두 읽고 쓸 수 있도록 바꿔줌:

- 이후 GoogleService.plist 다운하여 xcode runner파일에 추가해줬음. (보완 필요,  pod install 전, android도 해줘야함 - 이건 있네 이미)

- 어제에 이어 오늘 에뮬레이터를 실행하니 minSdkVersion 설정을 하라 해서, buiild.gradle파일에서 21로 하드코딩 해줌.

- 새로 바꾼 login의 뼈대임:

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

import '../../core/theme/constant/app_colors.dart';
import '../../core/theme/constant/app_icons.dart';
import '../widgets/text_field_input.dart';

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

  @override
  State<LoginScreen2> createState() => _LoginScreen2State();
}

class _LoginScreen2State extends State<LoginScreen2> {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();

  @override
  void dispose() {
    super.dispose();
    _emailController.dispose();
    _passwordController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 32),
          width: double.infinity,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Flexible(flex: 2, child: Container()),
              SvgPicture.asset(
                AppIcons.navHomeOn,
                height: 64,
              ),
              const SizedBox(
                height: 64,
              ),
              TextFieldInput(
                textEditingController: _emailController,
                hintText: 'Enter your email',
                textInputType: TextInputType.emailAddress,
              ),
              const SizedBox(
                height: 24,
              ),
              TextFieldInput(
                hintText: 'Enter your password',
                textInputType: TextInputType.text,
                textEditingController: _passwordController,
                isPass: true,
              ),
              const SizedBox(
                height: 24,
              ),
              InkWell(
                onTap: () {},
                child: Container(
                  child: const Text('Log in'),
                  width: double.infinity,
                  alignment: Alignment.center,
                  padding: const EdgeInsets.symmetric(vertical: 12),
                  decoration: const ShapeDecoration(
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(
                        Radius.circular(4),
                      ),
                    ),
                    color: AppColors.blueColor,
                  ),
                ),
              ),
              const SizedBox(
                height: 12,
              ),
              Flexible(child: Container(), flex: 2),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    child: const Text("Don't have an account?"),
                    padding: const EdgeInsets.symmetric(
                      vertical: 8,
                    ),
                  ),
                  GestureDetector(
                    onTap: () {},
                    child: Container(
                      child: const Text(
                        "Sign up.",
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      padding: const EdgeInsets.symmetric(
                        vertical: 8,
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

- 아래는 signup 스크린임:

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

import '../../core/theme/constant/app_colors.dart';
import '../../core/theme/constant/app_icons.dart';
import '../widgets/text_field_input.dart';

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

  @override
  State<SignupScreen> createState() => _SignupScreenState();
}

class _SignupScreenState extends State<SignupScreen> {
  final TextEditingController _emailController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final TextEditingController _bioController = TextEditingController();
  final TextEditingController _usernameController = TextEditingController();

  @override
  void dispose() {
    super.dispose();
    _emailController.dispose();
    _passwordController.dispose();
    _bioController.dispose();
    _usernameController.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 32),
          width: double.infinity,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Flexible(flex: 2, child: Container()),
              SvgPicture.asset(
                AppIcons.navHomeOn,
                height: 64,
              ),
              const SizedBox(
                height: 64,
              ),
              Stack(
                children: [
                  const CircleAvatar(
                    radius: 64,
                    backgroundImage:
                        NetworkImage('https://i.stack.imgur.com/l60Hf.png'),
                    backgroundColor: Colors.red,
                  ),
                  Positioned(
                    bottom: -10,
                    left: 80,
                    child: IconButton(
                      onPressed: () {},
                      icon: const Icon(Icons.add_a_photo),
                    ),
                  )
                ],
              ),
              const SizedBox(
                height: 24,
              ),
              TextFieldInput(
                textEditingController: _usernameController,
                hintText: 'Enter your username',
                textInputType: TextInputType.text,
              ),
              const SizedBox(
                height: 24,
              ),
              TextFieldInput(
                textEditingController: _emailController,
                hintText: 'Enter your email',
                textInputType: TextInputType.emailAddress,
              ),
              const SizedBox(
                height: 24,
              ),
              TextFieldInput(
                hintText: 'Enter your password',
                textInputType: TextInputType.text,
                textEditingController: _passwordController,
                isPass: true,
              ),
              const SizedBox(
                height: 24,
              ),
              TextFieldInput(
                hintText: 'Enter your bio',
                textInputType: TextInputType.text,
                textEditingController: _bioController,
                isPass: true,
              ),
              const SizedBox(
                height: 24,
              ),
              InkWell(
                onTap: () {},
                child: Container(
                  child: const Text('Log in'),
                  width: double.infinity,
                  alignment: Alignment.center,
                  padding: const EdgeInsets.symmetric(vertical: 12),
                  decoration: const ShapeDecoration(
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.all(
                        Radius.circular(4),
                      ),
                    ),
                    color: AppColors.blueColor,
                  ),
                ),
              ),
              const SizedBox(
                height: 12,
              ),
              Flexible(child: Container(), flex: 2),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Container(
                    child: const Text("Don't have an account?"),
                    padding: const EdgeInsets.symmetric(
                      vertical: 8,
                    ),
                  ),
                  GestureDetector(
                    onTap: () {},
                    child: Container(
                      child: const Text(
                        "Sign up.",
                        style: TextStyle(
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      padding: const EdgeInsets.symmetric(
                        vertical: 8,
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

- 이후 firebase auth 파일을 만들어 실제로 연동하는 작업을 함.

- 우선 auth와 firestore에 사용자 정보가 들어오게끔 함. auth methods 파일을 별도의 resources directory에 담아서 collection과 doc에 들어갈 값을 정의해줌.

- 작은 팁으로, firebase auth와 firestore에 데이터 저장시 add가 아닌 set methods를 쓰는 이유는 set 메서드를 써야 auth와 firestore에 같은 uid가 할당됨. Add로 구현시 firestore에 랜덤한 uid가 부여됨. 요런식:

import 'dart:typed_data';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class AuthMethods {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final FirebaseFirestore _firestore = FirebaseFirestore.instance;

  Future<String> signupUser({
    required String email,
    required String password,
    required String username,
    required String bio,
    // required Uint8List file,
  }) async {
    String res = "Some error occurred";
    try {
      if(email.isNotEmpty || password.isNotEmpty || username.isNotEmpty || bio.isNotEmpty) {
        UserCredential cred = await _auth.createUserWithEmailAndPassword(email: email, password: password);

        print(cred.user!.uid);
        await  _firestore.collection('users').doc(cred.user!.uid).set({
          'username': username,
          'uid': cred.user!.uid,
          'email': email,
          'bio': bio,
          'followers': [],
          'following': [],
        });

        res = "success";
      }
    } catch(err) {
      res = err.toString();
    }
    return res;
  }
}

 

- image picker 적용하여 프로필 사진 선택할 수 있게 하였음. ios의 경우 info.plist에 아래를 추가해줘야 했음:

<key>NSPhotoLibraryUsageDescription</key>
<string>Photo Library Usage</string>
<key>NSCameraUsageDescription</key>
<string>Camera Usage</string>
<key>NSMicrophoneUsageDescription</key>
<string>Mic Usage</string>

- 위와 유사하게 storage methods 파일을 만들어서 작업하였고, 성공적으로 Firebase storage에 이미지까지 저장되는 것을 확인함.

 

 

- 다음 순서로, 로그인 기능 구현하고 로그인 된 authentication이 유지되도록 했어야 하는데 좀 더 고민할 필요가 있어 auth state 유지하는 부분은 일단 건너뜀.

 

- User data를 models로 분리해서 따로 관리하게 만듦.

- User Data의 상태관리 설정함 w/ Provider. 얘도 건너뜀. 어떤 상태관리 패키지 쓸 지 고민 후 

 

 

라고 했는데, 상태관리가 내가 벤치마킹하고 있는 자료별로 다 다르다 보니 디렉토리 아키텍처부터 꼬여서 지금 손을 쓸 수가 없는 상태임.

나중에 복구 및 적용하면 되겠지 생각했는데 무리가 있어 보이고, 아예 전략을 바꿔서 하나를 완벽히 벤치마킹하고 그 뒤에 손을 쓰는게 맞다는 판단임.

그래서 여기서 아예 폴더를 뒤집고 다시 시작할 생각임. 매우 슬픔...!

 

- 소감은,,, 일단 환경설정도 녹록치 않고, 폴더 구조를 처음부터 전략적으로 가지고 가기엔 내가 아직 많이 뉴비라 무리임을 인지함. 그래서 아예 간단하게 기능구현 위주로 의도에 맞게 어느정도 개발한 후 클린 아키텍처를 적용하고, 리팩토링을 할 생각.

- 2차 시도 및 추후의 프로젝트는 성공적이기를 기원함.

관련글 더보기