Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- snackbar
- setstate
- divider
- runTransaction
- user
- transform
- swift 문법
- Navigator
- 문법
- Camera
- switch
- consumer
- reference
- enum
- borderRadius
- Swift
- controller
- signout
- datetime
- changenotifier
- Firebase
- multiprovider
- permission
- globalkey
- Stream
- provider
- ListView.builder
- Snapshot
- changenotifierprovider
- platformexception
Archives
- Today
- Total
코딩하는 제리
[Flutter/Project](Instagram Clone) Stream과 MultiProvider를 이용한 유저 상태 표시 본문
Flutter/Project_InstaClone(완)
[Flutter/Project](Instagram Clone) Stream과 MultiProvider를 이용한 유저 상태 표시
JerryCho 2021. 2. 17. 13:02
Stream을 통해서 데이터가 바뀔때마다 불러옴.
소스코드 및 pubspec.yaml
// repo/helper/transformers.dart
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_project_IJ/models/firestore/user_model.dart';
class Transformers {
// DocumentSnapshot을 UserModel로 바꿈.
// fromHandlers -> 생성자
final toUser = StreamTransformer<DocumentSnapshot, UserModel>.fromHandlers(
handleData: (snapshot, sink) async {
// 변화된 snapshot을 sink에 넣어서 데이터를 보냄.
sink.add(UserModel.fromSnapshot(snapshot));
});
}
// main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_project_IJ/models/firebase_auth_state.dart';
import 'package:flutter_project_IJ/models/user_model_state.dart';
import 'package:flutter_project_IJ/repo/user_network_repository.dart';
import 'package:flutter_project_IJ/screens/auth_screen.dart';
import 'package:flutter_project_IJ/widgets/my_gallery.dart';
import 'package:flutter_project_IJ/widgets/my_progress_indicator.dart';
import 'package:provider/provider.dart';
import 'home_page.dart';
import 'constants/colors.dart';
void main() async {
// 파이어베이스 사용을 위한 호출
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
FirebaseAuthState _firebaseAuthState = FirebaseAuthState();
Widget _currentWidget;
@override
Widget build(BuildContext context) {
_firebaseAuthState.watchAuthChange();
return MultiProvider(
providers: [
// vallue() -> 기존에 생성되어 있는 인스턴스를 사용.
ChangeNotifierProvider<FirebaseAuthState>.value(
value: _firebaseAuthState,
),
// (create:) -> 인스턴스 생성과 동시에 사용.
ChangeNotifierProvider<UserModelState>(
create: (_) => UserModelState(),
)
],
child: MaterialApp(
home: Consumer<FirebaseAuthState>(
builder: (BuildContext context, FirebaseAuthState firebaseAuthState,
Widget child) {
switch (firebaseAuthState.firebaseAuthStatus) {
case FirebaseAuthStatus.signout:
_currentWidget = AuthScreen();
break;
case FirebaseAuthStatus.singin:
// 로그인이 되었을 때 UserModel 업데이트
// 유저의 uid를 가져오려면 firebase_auth_state.dart 파일에서 User를 가져와야함.
userNetworkRepository
.getUserModelStreams(firebaseAuthState.firebaseUser.uid)
.listen((userModel) {
Provider.of<UserModelState>(context, listen: false)
.userModel = userModel;
// notifyListeners()로 받아온 데이터라면, listen: false를 입력.
});
_currentWidget = HomePage();
break;
default:
_currentWidget = MyProgressIndicator();
}
return AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: _currentWidget,
);
},
child: HomePage(),
),
theme: ThemeData(primarySwatch: white),
),
);
}
}
// widgets/profile_body.dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_project_IJ/constants/common_size.dart';
import 'package:flutter_project_IJ/constants/screen_size.dart';
import 'package:flutter_project_IJ/models/user_model_state.dart';
import 'package:flutter_project_IJ/screens/profile_screen.dart';
import 'package:flutter_project_IJ/widgets/rounded_avatar.dart';
import 'package:provider/provider.dart';
// 가독성을 위해서 사용.
enum SelectedTab { left, right }
class ProfileBody extends StatefulWidget {
// 함수로 받아옴
final Function onMenuChanged;
const ProfileBody({Key key, this.onMenuChanged}) : super(key: key);
@override
_ProfileBodyState createState() => _ProfileBodyState();
}
class _ProfileBodyState extends State<ProfileBody>
with TickerProviderStateMixin /* AnimationController를 사용하기 위해 추가 */ {
// bool selectedLeft = true;
SelectedTab _selectedTab = SelectedTab.left;
double _leftImagesPageMargin = 0;
double _rightImagesPageMargin = size.width;
// AnimatedIcon을 사용하기위한 AnimationController
AnimationController _iconAnimationController;
@override
void initState() {
// 해당 state가 새로 생성되었을 때 실행.
// this는 _ProfileBodyState 클래스를 가르킴
// AnimationController를 사용하기 위해서 TickerProvider가 필요.
// 해당 클래스에 with 키워드로 TickerProviderStateMixin를 사용.
_iconAnimationController =
AnimationController(vsync: this, duration: duration);
super.initState();
}
@override
void dispose() {
// State가 제거될 때 실행.
_iconAnimationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SafeArea(
// SafeArea() 디바이스 상단바와 거리를 띄움
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_appbar(),
Expanded(
// Expanded()로 CustomScrollView()가 빈공간을 차지하도록 해야 표시됨.
child: CustomScrollView(
// CustomScrollView() 스크롤 가능한 뷰들을 섞어서 사용할 때.
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
Row(
children: [
Padding(
padding: const EdgeInsets.all(common_gap),
child: RoundedAvatar(
size: 80,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: common_gap),
child: Table(
children: [
TableRow(children: [
_valueText('123'),
_valueText('456'),
_valueText('789'),
]),
TableRow(children: [
_labelText('Post'),
_labelText('Followers'),
_labelText('Following'),
])
],
),
),
),
],
),
_username(context),
_userBio(),
_editProfileBtn(),
_tabButtons(),
_selectedIndicator(),
]),
),
_imagesPager(),
],
),
),
],
),
);
}
Row _appbar() {
return Row(
children: [
SizedBox(width: 44),
Expanded(
child: Text(
'jerrycho',
textAlign: TextAlign.center,
),
),
IconButton(
// menu_close 메뉴 아이콘에서 X 모양 아이콘으로 변경하는 애니메이션 아이콘
// AnimatedIcon을 사용하기 위해서는 AnimationController가 필요.
icon: AnimatedIcon(
icon: AnimatedIcons.menu_close,
progress: _iconAnimationController,
),
onPressed: () {
// 아이콘이 선택될 때 함수를 실행.
// widget. 다른 위젯 클래스의 데이터를 사용할 때.
widget.onMenuChanged();
/*
애니메이션 상태가
끝난 상태로 멈춰 있으면(completed)
다시 돌아오게(reverse) 하고,
시작전 상태로 멈춰 있으면(dismissed)
시작하게(forward) 한다.
*/
_iconAnimationController.status == AnimationStatus.completed
? _iconAnimationController.reverse()
: _iconAnimationController.forward();
},
)
],
);
}
Text _valueText(String value) => Text(
value,
style: TextStyle(fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
);
Text _labelText(String label) => Text(
label,
style: TextStyle(fontWeight: FontWeight.w300, fontSize: 12),
textAlign: TextAlign.center,
);
SliverToBoxAdapter _imagesPager() {
return SliverToBoxAdapter(
// SliverToBoxAdapter() 일반적인 뷰를 Sliver 뷰로 변환.
child: Stack(
children: <Widget>[
AnimatedContainer(
// 애니메이션 길이 0.3초
duration: duration,
//transform: Matrix4.translationValues(x, y, z),
transform: Matrix4.translationValues(_leftImagesPageMargin, 0, 0),
curve: Curves.fastOutSlowIn,
child: _images(),
),
AnimatedContainer(
// 애니메이션 길이 0.3초
duration: duration,
//transform: Matrix4.translationValues(x, y, z),
transform: Matrix4.translationValues(_rightImagesPageMargin, 0, 0),
curve: Curves.fastOutSlowIn,
child: _images(),
),
],
),
);
}
GridView _images() {
return GridView.count(
crossAxisCount: 3,
physics: NeverScrollableScrollPhysics() /* GridView의 스크롤을 사용하지 않음 */,
shrinkWrap: true /* 데이터의 양 만큼만 공간을 차지하게함 */,
childAspectRatio: 1 /* 이미지 비율 */,
children: List.generate(
// List.generate() 리스트 생성
30 /* 30개의 아이템 */,
(index) => CachedNetworkImage(
fit: BoxFit.cover,
imageUrl: "https://picsum.photos/id/$index/100/100",
),
),
);
}
Widget _selectedIndicator() {
return AnimatedContainer(
duration: duration,
alignment: _selectedTab == SelectedTab.left
? Alignment.centerLeft
: Alignment.centerRight,
curve: Curves.fastOutSlowIn,
child: Container(
height: 5,
width: size.width / 2,
color: Colors.black87,
),
);
}
Row _tabButtons() {
return Row(
children: [
Expanded(
child: IconButton(
color: _selectedTab == SelectedTab.left
? Colors.black
: Colors.black38,
icon: ImageIcon(AssetImage('assets/images/grid.png')),
onPressed: () {
setState(() {
// StatefulWidget의 상태변화 적용.
_tabSelected(SelectedTab.left);
});
},
),
),
Expanded(
child: IconButton(
color: _selectedTab == SelectedTab.left
? Colors.black38
: Colors.black,
icon: ImageIcon(AssetImage('assets/images/saved.png')),
onPressed: () {
setState(() {
// StatefulWidget의 상태변화 적용.
_tabSelected(SelectedTab.right);
});
},
),
),
],
);
}
_tabSelected(SelectedTab selectedTab) {
switch (selectedTab) {
case SelectedTab.left:
_selectedTab = SelectedTab.left;
_leftImagesPageMargin = 0;
_rightImagesPageMargin = size.width;
break;
case SelectedTab.right:
_selectedTab = SelectedTab.right;
_leftImagesPageMargin = -size.width;
_rightImagesPageMargin = 0;
break;
}
}
Widget _editProfileBtn() {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: common_gap,
vertical: common_xxs_gap,
),
child: SizedBox(
height: 24,
child: OutlineButton(
onPressed: () {},
borderSide: BorderSide(color: Colors.black45),
shape: RoundedRectangleBorder(
// RoundedRectangleBorder() 둥근 모서리 직사각형.
borderRadius: BorderRadius.circular(6),
),
child: Text(
'Edit Profile',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
);
}
Widget _username(BuildContext context) {
UserModelState userModelState = Provider.of<UserModelState>(context);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: common_gap),
child: Text(
userModelState == null || userModelState.userModel == null
? ""
: userModelState.userModel.username,
style: TextStyle(fontWeight: FontWeight.bold),
),
);
}
Widget _userBio() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: common_gap),
child: Text(
'username',
style: TextStyle(fontWeight: FontWeight.w400),
),
);
}
}
// repo/user_network_repository.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_project_IJ/constants/firestore_keys.dart';
import 'package:flutter_project_IJ/models/firestore/user_model.dart';
import 'package:flutter_project_IJ/repo/helper/transformers.dart';
// with -> 해당 클래스의 모든 기능을 사용.
class UserNetworkRepository with Transformers {
// 유저가 생성되어 있는지 확인
Future<void> attempCreateUser({String userKey, String email}) async {
final DocumentReference userRef =
FirebaseFirestore.instance.collection(COLLECTION_USERS).doc(userKey);
// 해당 레퍼런스의 데이터를 가져옴
DocumentSnapshot snapshot = await userRef.get();
// 해당 데이터가 존재하지 않으면 생성
if (!snapshot.exists) {
return await userRef.set(UserModel.getMapForeCreateUser(email));
}
}
Stream<UserModel> getUserModelStreams(String userKey) {
return FirebaseFirestore.instance
.collection(COLLECTION_USERS)
.doc(userKey)
.snapshots()
.transform(toUser);
// transform -> 필요한 데이터 타입으로 변경시켜줌.
/*
get() -> 유저 데이터를 Future로 한 번만 가져옴.
snapshots() -> 유저 데이터를 Stream으로 필요할 때 계속 가져옴.
*/
}
}
UserNetworkRepository userNetworkRepository = UserNetworkRepository();
'Flutter > Project_InstaClone(완)' 카테고리의 다른 글
[Flutter/Project](Instagram Clone) 로그인 시 유저데이터 확인 (0) | 2021.02.17 |
---|---|
[Flutter/Project](Instagram Clone) Stream 데이터 구독 끊기 (0) | 2021.02.17 |
[Flutter/Project](Instagram Clone) Firestore 회원가입 유저 데이터 생성 (0) | 2021.02.16 |
[Flutter/Project](Instagram Clone) Firestore 데이터 입/출력 (0) | 2021.02.11 |
[Flutter/Project](Instagram Clone) FireStore UserModel 및 상태 파일 (0) | 2021.02.11 |
Comments