처음부터 부족한 이해력으로 클린아키텍처를 적용하면서 개발하느라 애로사항이 많아 일단 기능개발에 초점을 맞추기로 함.
로그인 화면부터 다시 작업하게 됨.
- 일단 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차 시도 및 추후의 프로젝트는 성공적이기를 기원함.
[Donfo] BottomNavigationBar, Firebase 환경설정, UI 개발 (0) | 2024.05.14 |
---|---|
[Donfo] 환경설정 및 구조 잡기 (0) | 2024.05.13 |
[React Native] Expo CLI / npx 명령어 (0) | 2024.04.11 |