-
Flutter - 코딩마스터하기|사진 업로드,게시하기2개발/Flutter 2021. 11. 16. 21:09반응형
horizontal scroll tags
여기서 태그들은 라이브러리를 사용할 것이다. pub.dev에서 flutter_tags를 사용해보자. 강의는 0.4.8+2이고 나는 최신 버전인 flutter_tags: ^0.4.9+1 를 사용하였다. 그리고 share_post_screen으로 가서 Tags()를 만들어 준다. 우리는 가로 스크롤이 되게 할 것이므로 horizontalScroll을 true로 해준다. 그리고 우선 여기에 사용할 List를 상단에 만들어 준다. List<String> _tagItems로 []에 아무 단어 30개 정도를 넣어준다. 그리고 Tags에서 itemCount에 _tagItems.length를 해준다. 그리고 이 Tags의 높이 부분인 heightHorizontalScroll은 30으로 해준다. itemBuilder에 (index)=> ItemTags()를 만들어 준다. title에는 _tagItems[index], index 에는 index를 해준다. color은 선택된 아이템 상태로 Colors.red를 해주고 activeColor은 기본 상태의 색인데 grey[200]정도로 해준다. 그외에도 color의 옵션들이 여러개 있는데 직접 색을 넣어보면서 해보면 된다. borderRadius는 circular(4)로 해주고 그림자가 너무 많이 있어서 elevation은 2정도로 해주면 된다.
List<String> _tagItems = [ 'quam', 'elementum', 'pulvinar', 'etiam', 'non', 'quam', 'lacus', 'suspendisse', 'faucibus', 'interdum', 'posuere', 'lorem', 'ipsum', 'dolor', 'sit', 'amet', 'consectetur', 'adipiscing', 'elit', 'duis', 'tristique', 'sollicitudin', 'nibh', 'sit', 'amet', 'commodo', 'nulla', 'facilisi', 'nullam', 'vehicula', 'ipsum', 'a', 'arcu', 'cursus', 'vitae', 'congue', 'mauris', 'rhoncus', 'aenean', 'vel', ]; Tags( horizontalScroll: true, itemCount: _tagItems.length, heightHorizontalScroll: 30, itemBuilder: (index) => ItemTags( title: _tagItems[index], index: index, splashColor: Colors.grey[800], color: Colors.red, activeColor: Colors.grey[200], textActiveColor: Colors.black87, borderRadius: BorderRadius.circular(4), elevation: 2,//떠있는 높이. 그림자가 더 많이 나옴 ), ),
section switch layout
태그 부분 밑에 각 sns에 같이 올리는 스위치 section부분을 만들어 보자. 전에 만들었던 _sectionButton 부분을 복사해서 _sectionSwitch를 만들어주고 trailing 부분의 아이콘을 삭제한뒤에 CupertionoSwitch를 넣어준다. 그냥 Swtich를 써도 되지만 IOS 모양의 스위치를 넣어주려면 CupertionoSwitch를 써주면 된다. value는 true로, onChanged는 (onValue){}를 해주면 정상적으로 나온다. 그리고 이부분은 스위치가 움직여야 하므로 stateful로 변경해줘야 한다. 여기서 전체를 변경 시킬 수 있지만 _sectionSwitch의 return 부분을 flutter widget로 빼준다. 그리고 stateful 로 변경 해주면 된다. 기본 클래스에 final String title를 만들어 주고 key 부분에 this.title를 {}밖에 넣어줘서 필수로 만들어 준다. 그리고 state 클래스 네에 bool checked = false;를 만들어 주고 Text()에 title를 widget.title로 해주면 된다. CupertinoSwitch에 value에는 checked를 넣어주고 onChanged에 setState를 생성해준뒤 checked = onValue;를 넣어주면 된다. 기존 _sectionSwitch는 삭제해주고 _tags()아래 _divider를 하나 생성해 준뒤 SectionSwtich를 3개 만들어 주고 각각 Facebook, Instagram, Tumblr을 넣어준뒤 실행하면 스위치도 잘 바뀌고 정상 작동하는 것을 볼 수 있다. 태그와 스위치 사이가 너무 좁아서 _tags()와 _divider사이에 SizedBox에 height를 common_s_gap만큼 주면 된다.
body: ListView( children: [ _captionWithImage(), _divider, _sectionButton('Tag People'), _divider, _sectionButton('Add Location'), _tags(), SizedBox(height: common_s_gap,), _divider, const SectionSwitch('Facebook'), const SectionSwitch('Instagram'), const SectionSwitch('Tumblr'), _divider, ], ),
class SectionSwitch extends StatefulWidget { final String title; const SectionSwitch( this.title,{ Key key, }) : super(key: key); @override State<SectionSwitch> createState() => _SectionSwitchState(); } class _SectionSwitchState extends State<SectionSwitch> { bool checked = false; @override Widget build(BuildContext context) { return ListTile( title: Text( widget.title, style: TextStyle(fontWeight: FontWeight.w400), ), trailing: CupertinoSwitch( //그냥 switch 써도 되지만 IOS 모양 쓰려면 이걸로 value: checked, onChanged: (onValue){ setState(() { checked = onValue; }); }, ), dense: true, contentPadding: EdgeInsets.symmetric( horizontal: common_gap, ), ); } }
resize image
스토리지에 이미지를 올리기 전 이미지를 리사이즈 해보자. repo - helper 폴더에 image_helper.dart 새파일을 만들어 준다. decodeImage를 사용하기 위해서 import 'package:image/image.dart';를 해주자. 그냥 사용하려면 잘 나오지 않아 미리 import 해주자. 우선 오리지날 파일을 byte로 읽어와서 Image object로 변경해준다. 그 뒤 300x300으로 사이즈를 줄여 준다. 줄여준 파일을 기존 오리지날 파일의 경로를 읽어 온다. 이때 0부터 length-3까지 읽어온다. 이러면 사진 찍었을때 png인데 png 전까지 읽어 와주고 거기에 jpg로 해줘서 파일을 jpg로 변경해준다. 그리고 encodeJpg로 퀄리티를 50으로 낮춰준 후 File 형식으로 다시 만들어주고 리턴해 주는 방식이다.(단 문제는 이 부분이 오래 걸릴수 있음, 이부분을 하면서 프로세싱이 안멈추게 할것인가를 다음시간에 배울 것임)
File getResizedImage(File originImage){ Image image = decodeImage(originImage.readAsBytesSync()); //오리지날 파일을 바이트로 읽어와서 Image object 로 읽음 Image resizedImage = copyResizeCropSquare(image, 300); //사이즈 줄임 정사각형으로 만들어줌 File resizedFile = File(originImage.path.substring(0, originImage.path.length-3)+"jpg"); // 0 처음 부터 오른쪽에서 -3 곧 png부분 제외하고 가져와서 jpg로 변경 resizedFile.writeAsBytesSync(encodeJpg(resizedImage, quality: 50)); //퀄리티 줄임 return resizedFile; }
use isolate to resize the image
repo 폴더에 image_network_repository.dart 파일을 만들어 준다. firestore 와 연결해줄 부분이다. 그 부분에서 try catch를 이용해서 할 것이다. 우선 클래스 네에 Future<void> uploadImageNCreateNewPost(File originImage)를 만들어 주고 async 해준다. 그리고 그안에 try catch를 만들어 준다. 그리고 try안에 final File resize = await compute()를 만들어 준다. compute 안에는 메소드, 파일 순서로 넣어 주면 된다. 그리고 파일크기가 얼마나 변경 되었는지 확인하기 위해 print해본다. share_post_screen에 build 의 return위에 imageNetworkRepository.uploadImageNCreateNewPost(imageFile); 를 넣어줘서 리사이즈 되게 해준다. 실행해 보면 사이즈가 줄어든 것을 알 수 있다.
class ImageNetworkRepository { Future<void> uploadImageNCreateNewPost(File originImage) async { try { final File resized = await compute( getResizedImage, originImage); // compute는 메소드명, 거기에 들어갈 파일? 로 전달해주면됨. originImage .length() .then((value) => print('original image size: $value')); resized.length().then((value) => print('resized image size: $value')); } catch (e) {} } } ImageNetworkRepository imageNetworkRepository = ImageNetworkRepository();
show loading while processing(modal bottom sheet)
사진 프로세스를 처리하는 동안 로딩바를 보여주자. 우선 위젯 build 안에서 업로드 하던 부분을 버튼의 onPressed 안에 집어 넣어주고, image_network_repository에서 try의 가장 하단에 await Future.delayed(Duration(second:3)) 으로 프로세스 진행 후 3초정도 기다리게 해준다. 로딩바를 보여주는 방법이 여러개인데 이번에는 showModalBottomSheet를 사용해서 해보자. onPressed 안에 showModalBottomSheet를 만들어 주고 context: context,를 해준다. builder은 (_) => MyProgerssIndicator(),를 해준다. 이때 ()안에 _를 해주는데 원래는 context를 넣지만 여기선 사용을 하지 않으므로 그냥 _를 넣어서 사용해준다. 그리고 isDismissible: false로 해준다. 이것은 해당 모달창이 올라왔을때 바깥부분을 누르면 꺼지게 하는 옵션인데 로딩동안 기다려야 하므로 false를 해준다. enableDrag도 같은 이유로 false로 해준다. 로딩창이 imageNetworkRepository.uploadImageNCreateNewPost(imageFile);를 기다리고 해야 하므로 앞에 await를 해주고 onPressed에 () async {}를 해주면 된다. 그리고 창을 닫아주기 위해 await 후에 Navigator.of(context).pop();를 해준다. 실행해보면 share시에 모달창으로 로딩이 뜨고 3초 후 정도에 꺼지는 것을 알 수있다.
FlatButton( onPressed: () async { showModalBottomSheet( context: context, builder: (_) => MyProgressIndicator(), //원래 빌더에 ()안에 context가 와야하는데 context를 사용 안하므로 _ 로 해준다 isDismissible: false, //이 창의 바깥 눌렀을때 끝나게 해줄것이냐 enableDrag: false, //드래그를 하게 해줄 것이냐. ); await imageNetworkRepository.uploadImageNCreateNewPost(imageFile); Navigator.of(context).pop(); //await 되고 나면 pop으로 꺼짐짐 }, child: Text( "Share", textScaleFactor: 1.4, style: TextStyle( color: Colors.blue, ), ), ),
class ImageNetworkRepository { Future<void> uploadImageNCreateNewPost(File originImage) async { try { final File resized = await compute( getResizedImage, originImage); // compute는 메소드명, 거기에 들어갈 파일? 로 전달해주면됨. originImage .length() .then((value) => print('original image size: $value')); resized.length().then((value) => print('resized image size: $value')); await Future.delayed(Duration(seconds: 3,)); } catch (e) {} } }
upload image to firebase storage
firebase storage에 업로드 하기위해서 라이브러리를 사용하자. 해본결과 firebase_storage: ^3.1.6 에서 정상작동하므로 우선 이 버전을 사용해 준다. 기존 try문에서 resized를 해주는 부분을 빼고 지운다. 그리고 postKey를 넘겨 주는 부분과 putFile해주는 부분을 추가해준다. share_post_screen에서 postKey를 넘겨준다.
class ImageNetworkRepository { Future<StorageTaskSnapshot> uploadImageNCreateNewPost(File originImage, {@required String postKey}) async { try { final File resized = await compute( getResizedImage, originImage); // compute는 메소드명, 거기에 들어갈 파일? 로 전달해주면됨. final StorageReference storageReference = FirebaseStorage().ref().child(_getImagePathByPostKey(postKey)); final StorageUploadTask uploadTask = storageReference.putFile(resized); return uploadTask.onComplete; } catch (e) { print(e); return null; } } String _getImagePathByPostKey(String postKey) => 'post/$postKey/post.jpg'; } ImageNetworkRepository imageNetworkRepository = ImageNetworkRepository();
위코드처럼 작성후 uploadImageNCreateNewPost를 넘겨주는 부분에서 postKey:postKey까지 해주고 실행하면 로딩바가 나오고 정상적으로 스토리지에 폴더가 생성된것을 볼 수 있다. 안을 확인하면 업로드가 된것을 볼 수 있다.
get image download link and show the image
업로드 된 이미지를 앱으로 가져와서 보여줘 보자. image_network_repository.dart에서 하단에 메소드를 하나 만들어 주자. Future<dynamic>으로 getPostImageUrl(String postKey) 라는 메소드를 만들자. 다운로드 Url을 가져오는 메소드 이다. return에 FirebaseStorage().ref().child(_getImagePathByPostKey(postKey)).getDownloadURL();로 다운로드 경로를 가져오면 된다. 이제 이 부분을 post.dart에서 _postImage()에서 불러오면 된다. 우선 그것을 위해서는 CachedNetworkImage를 StreamBuilder로 감싸준다. 그리고 StreamBuilder을 FutureBuilder<dynamic>으로 변경해주고 stream 옵션을 future로 변경해준다. 그리고 CachedNetworkImage였던 위젯을 Widget로 변경해준다. future 부분에 imageNetworkRepository.getPostImageUrl('이미지가 저장된 postKey'),를 해줘서 Future<dynamic>의 값을 가져온다. 그리고 builder안에 if(snapshot.hasData)일때 기존 return 부분을 다 넣어주고, else 에 MyProgressIndicator를 return해준다. 여기서 로딩 부분이 겹치므로 위젯의 상단에 Widget progress = MyProgressIndicator( containerSize: size.width, );를 만들어 주고 CachedNetworkImage의 placeholder안의 return에 progress와 방금 만들었던 if문의 else에 return에 progress를 넣어주면 된다. 그럼 정상적으로 피드화면에 방금찍은 사진이 보이는것이 보인다.
post.dart Widget _postImage() { Widget progress = MyProgressIndicator( containerSize: size.width, ); return FutureBuilder<dynamic>( future: imageNetworkRepository .getPostImageUrl('1637063370169_QMaZBYr2CwfIH3i8ykqLSBWG83l2'), builder: (context, snapshot) { if (snapshot.hasData) { return CachedNetworkImage( //flutter의 이미지는 캐시를 안함 창이 바뀔때마다 새로 불러옴. 그래서 저장해놓은 이미지를 쓰기 위해서 이걸 씀. imageUrl: snapshot.data.toString(), placeholder: (BuildContext context, String url) { return progress; }, imageBuilder: (BuildContext context, ImageProvider imageProvider) { return AspectRatio( aspectRatio: 1, child: Container( decoration: BoxDecoration( image: DecorationImage( image: imageProvider, fit: BoxFit.cover, ), ), ), ); }, ); } else { return progress; } }, ); }
image_network_repository.dart class ImageNetworkRepository { Future<StorageTaskSnapshot> uploadImageNCreateNewPost(File originImage, {@required String postKey}) async { try { final File resized = await compute( getResizedImage, originImage); // compute는 메소드명, 거기에 들어갈 파일? 로 전달해주면됨. final StorageReference storageReference = FirebaseStorage().ref().child(_getImagePathByPostKey(postKey)); final StorageUploadTask uploadTask = storageReference.putFile(resized); return uploadTask.onComplete; } catch (e) { print(e); return null; } } String _getImagePathByPostKey(String postKey) => 'post/$postKey/post.jpg'; Future<dynamic> getPostImageUrl(String postKey){ return FirebaseStorage().ref().child(_getImagePathByPostKey(postKey)).getDownloadURL(); } } ImageNetworkRepository imageNetworkRepository = ImageNetworkRepository();
다음시간엔 storage에 저장한 post 데이터를 데이터베이스에 저장하는법을 해보자.
반응형'개발 > Flutter' 카테고리의 다른 글
Flutter - 코딩마스터하기|파이어스토어를 통한 팔로/언팔로 (0) 2021.11.17 Flutter - 코딩마스터하기|사진 업로드,게시하기3 , storage에 저장한 Post의 데이터를 database에 저장하기 (0) 2021.11.16 Flutter - 코딩마스터하기|사진 업로드,게시하기1 (0) 2021.11.15 Flutter - 코딩마스터하기|Firebase Firestore 사용하기2 (0) 2021.11.15 Flutter - 코딩마스터하기|Firebase Firestore 사용하기 (0) 2021.11.11 댓글