Flutter Tutorial: Personalize Your Bisky Chat Profile Picture & Name!
In this highly requested blog, we'll guide you through the process of updating your profile picture and name in the Bisky Chat app using Flutter. Follow along step-by-step as we show you how to personalize your Bisky Chat experience with ease.
Key Topics covered:
- Add Update user events in AuthEvent
- Add Update user states in AuthState
- Listens Update user events in AuthBloc
- Create Update user page
- How to use Appwrite Storage
- Pick image from phone Gallery
Description:
The tutorial will walk you through the process of creating new events and states for updating user profile picture and name. The user will be able to pick an image from phone gallery.
By the end of this lesson, you will be able to use Appwrite Storage to store users profile pictures. Let's get started
Add image_picker dependency in your pubspec.yaml. We will use this dependency to pick an image from gallery
image_picker: ^0.8.7+5
Add the following permissions in ios/Runner/Info.plist
<key>NSPhotoLibraryUsageDescription</key>
<string>This app requires photos access to function properly.</string>
<key>NSCameraUsageDescription</key>
<string>This app requires camera access to function properly.</string>
<key>NSMicrophoneUsageDescription</key>
<string>This app requires mic access to function properly.</string>
Code Snippet(AuthEvent) modification:
Create the following events UpdateUserEvent, UploadProfilePictureEvent and GetExistingUserEvent
class UpdateUserEvent extends AuthEvent{
final String userId;
final String name;
final String profilePictureStorageId;
UpdateUserEvent(this.userId, this.name, this.profilePictureStorageId);
@override
List<Object> get props => [userId,name,profilePictureStorageId];
}
class UploadProfilePictureEvent extends AuthEvent{
final String path;
final String imageId;
final String imageExist;
UploadProfilePictureEvent( this.path, this.imageId, this.imageExist);
@override
List<Object> get props => [path,imageId,imageExist];
}
class GetExistingUserEvent extends AuthEvent{
final String userId;
GetExistingUserEvent(this.userId);
@override
List<Object> get props => [userId];
}
class GetExistingProfilePictureEvent extends AuthEvent{
final String imageId;
GetExistingProfilePictureEvent(this.imageId);
@override
List<Object> get props => [imageId];
}
Code Snippet(UserApwrite.dart):
Create a new model called UserApwrite.dart. We will use this model to persist the user in Appwrite database collection. Create a users collection on Appwrite dashboard with the same attributes that are in UserApwrite.dart
- lib/model/UserAppwrite.dart
import 'package:json_annotation/json_annotation.dart';
part 'UserAppwrite.g.dart';
@JsonSerializable()
class UserAppwrite{
String? userId;
String? name;
String? profilePictureStorageId;
UserAppwrite({this.userId, this.name, this.profilePictureStorageId});
factory UserAppwrite.fromJson(Map<String,dynamic> json)=>
_$UserAppwriteFromJson(json);
Map<String,dynamic> toJson() => _$UserAppwriteToJson(this);
}
- Add the following code below in lib/constant/strings.dart. You can update with your own id's from Appwrite
static const databaseId = "647f4492c0d82fd9982c";
static const collectionUsersId = "647f44af645618cff6a0";
static var displayName = "Name";
static String update = "Update";
static var profilePicturesBucketId = "647fb444a06408602d48";
Don't forget to run the following command in your terminal to generate UserAppwrite.g.dart flutter packages pub run build_runner build
Code Snippet(AuthBloc.dart) modification:
Add the following methods. The AuthBloc will be listening to the events. Based on the event fired by the user it will call the methods mapped to it. we are updating the User, getting an existing user, get existing profile picture if exists and also uploading profile picture to Appwrite Storage.
// add below code in the constructor
on<UpdateUserEvent>((event,state) async{
await updateUser(event,state);
});
on<UploadProfilePictureEvent>((event,state) async{
await uploadProfilePicture(event,state);
});
on<GetExistingUserEvent>((event,state) async{
await getExistingUser(event,state);
});
on<GetExistingProfilePictureEvent>((event,state) async{
await getExistingProfilePicture(event,state);
});
// add the above in the constructor
updateUser(UpdateUserEvent event, Emitter<AuthState> state) async {
Databases databases = Databases(appWriteClientService.getClient());
UserAppwrite userAppwrite= UserAppwrite(userId: event.userId,name: event.name,
profilePictureStorageId: event.profilePictureStorageId);
emit(LoadingUpdateUserAuthState());
try{
Account account = Account(appWriteClientService.getClient());
await account.updateName(
name: event.name
);
await databases.getDocument(databaseId: Strings.databaseId,
collectionId: Strings.collectionUsersId, documentId: event.userId);
Document document1 = await databases.updateDocument(databaseId: Strings.databaseId, collectionId: Strings.collectionUsersId,
documentId: event.userId, data: userAppwrite.toJson());
UserAppwrite userUpdate = UserAppwrite.fromJson(document1.data);
emit(SuccessUpdateUserAuthState(userUpdate));
}on AppwriteException catch (exception){
print(exception);
if(exception.code == 404){
Document document = await databases.createDocument(databaseId: Strings.databaseId, collectionId: Strings.collectionUsersId,
documentId: event.userId, data: userAppwrite.toJson());
UserAppwrite user = UserAppwrite.fromJson(document.data);
emit(SuccessUpdateUserAuthState(user));
}else{
emit(FailureUpdateUserAuthState("Failed to update your name. Please try again later"));
}
}
}
uploadProfilePicture(UploadProfilePictureEvent event, Emitter<AuthState> state) async {
try{
emit(LoadingUploadingProfilePictureAuthState());
Storage storage= Storage(appWriteClientService.getClient());
if(event.imageExist.isNotEmpty && event.imageId != event.imageExist){
await storage.deleteFile(bucketId: Strings.profilePicturesBucketId, fileId: event.imageExist);
}
print(Strings.profilePicturesBucketId);
File file = await storage.createFile(
bucketId: Strings.profilePicturesBucketId,
fileId: event.imageId,
file: InputFile(path: event.path, filename: '${event.imageId}.${getFileExtension(event.path)}'),
);
emit(SuccessUploadProfilePictureAuthState(file));
}on AppwriteException catch (exception){
print(exception);
emit(FailureUploadingProfilePictureAuthState("Failed to upload profile picture"));
}
}
String getFileExtension(String fileName) {
try {
return ".${fileName.split('.').last}";
} catch(e){
return "";
}
}
getExistingUser(GetExistingUserEvent event, Emitter<AuthState> state) async {
Databases databases = Databases(appWriteClientService.getClient());
try{
Document document = await databases.getDocument(databaseId: Strings.databaseId,
collectionId: Strings.collectionUsersId, documentId: event.userId);
UserAppwrite userUpdate = UserAppwrite.fromJson(document.data);
emit(SuccessgetExistingUserAuthState(userUpdate));
}on AppwriteException catch (exception){
print(exception);
}
}
getExistingProfilePicture(GetExistingProfilePictureEvent event, Emitter<AuthState> state) async {
try{
Storage storage= Storage(appWriteClientService.getClient());
Uint8List uint8list = await storage.getFilePreview(
bucketId: Strings.profilePicturesBucketId,
fileId: event.imageId);
emit(ExistingProfilePictureAuthState(uint8list));
}on AppwriteException catch (exception){
print(exception);
}
}
Code Snippet(UpdateNamePage):
- create below class
- lib/pages/update_name/UpdateNamePage.dart
import 'dart:io';
import 'package:auto_route/annotations.dart';
import 'package:chat_with_bisky/bloc/auth/auth_bloc.dart';
import 'package:chat_with_bisky/bloc/auth/auth_event.dart';
import 'package:chat_with_bisky/constant/strings.dart';
import 'package:chat_with_bisky/values/values.dart';
import 'package:chat_with_bisky/widget/custom_dialog.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl_phone_number_input/intl_phone_number_input.dart';
import 'package:introduction_screen/introduction_screen.dart';
import 'package:otp_text_field/otp_text_field.dart';
import 'package:otp_text_field/style.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'package:uuid/uuid.dart';
import '../../bloc/auth/auth_state.dart';
@RoutePage()
class UpdateNamePage extends StatefulWidget {
final String userId;
const UpdateNamePage(this.userId, {super.key});
@override
_UpdateNamePageState createState() {
return _UpdateNamePageState();
}
}
class _UpdateNamePageState extends State<UpdateNamePage> {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController controller = TextEditingController();
String profilePictureStorageId = "";
String imageExist = "";
String imageUrl = "https://www.w3schools.com/w3images/avatar3.png";
Uint8List? uint8list;
File? imageFile;
@override
Widget build(BuildContext context) {
return Scaffold(
body: BlocListener<AuthBloc, AuthState>(
listener: (context, state) {
if (state is LoadingUpdateUserAuthState) {
ChatDialogs.waiting(context, "loading.....");
} else if (state is SuccessUpdateUserAuthState) {
ChatDialogs.hideProgress(context);
// navigate to dashboard
} else if (state is FailureUpdateUserAuthState) {
ChatDialogs.hideProgress(context);
ChatDialogs.informationOkDialog(context,
title: "Error",
description: state.message,
type: AlertType.error);
} else if (state is SuccessgetExistingUserAuthState) {
setState(() {
controller.text = state.user.name ?? "";
profilePictureStorageId = state.user.profilePictureStorageId ?? "";
imageExist = state.user.profilePictureStorageId ?? "";
});
if (profilePictureStorageId.isNotEmpty) {
BlocProvider.of<AuthBloc>(context)
.add(GetExistingProfilePictureEvent(profilePictureStorageId));
}
} else if (state is ExistingProfilePictureAuthState) {
setState(() {
uint8list = state.uint8list;
});
} else if (state is SuccessUploadProfilePictureAuthState) {
BlocProvider.of<AuthBloc>(context).add(UpdateUserEvent(
widget.userId, controller.text, profilePictureStorageId));
}else if (state is FailureUploadingProfilePictureAuthState) {
ChatDialogs.informationOkDialog(context,
title: "Error",
description: state.message,
type: AlertType.error);
}
},
child: Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () async {
final ImagePicker picker = ImagePicker();
final XFile? image =
await picker.pickImage(source: ImageSource.gallery);
setState(() {
if (image != null) {
imageUrl = image.path;
imageFile = File(imageUrl);
}
});
},
child: CircleAvatar(
child: imageFile != null
? ClipOval(child: Image.file(imageFile!))
: ClipOval(
child: uint8list == null
? Image.network(imageUrl!)
: Image.memory(
uint8list!,
)),
radius: 80,
backgroundColor: Colors.grey),
),
const SizedBox(
height: Sizes.HEIGHT_10,
),
TextField(
controller: controller,
decoration: InputDecoration(
border: const OutlineInputBorder(),
labelText: Strings.displayName),
),
const SizedBox(
height: Sizes.HEIGHT_20,
),
ElevatedButton(
onPressed: () {
if(imageFile != null){
profilePictureStorageId = const Uuid().v4();
BlocProvider.of<AuthBloc>(context).add(UploadProfilePictureEvent(imageFile!.path,
profilePictureStorageId,imageExist));
}else{
BlocProvider.of<AuthBloc>(context).add(UpdateUserEvent(
widget.userId, controller.text, profilePictureStorageId));
}
},
child: const Text("Update"),
),
],
)
],
),
));
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
BlocProvider.of<AuthBloc>(context).add(GetExistingUserEvent(widget.userId));
}
}
Don't forget to run the following command in your terminal to generate UpdateNamePage route flutter packages pub run build_runner build. Let's add the route in lib/route/app_route/AppRouter.dart
AutoRoute(page: UpdateNamePage.page),
Conclusion:
We managed to upload a user profile picture in AuthBloc and fire Users events and listens to the Users states. The user is now able to update profile picture by picking it up from phone Gallery and updating Name that will be visible to other users when sending messages. In the next Tutorial we will create a Dashboard Page. Don't forget to share and join our Discord Channel, May you please subscribe to our YouTube Channel