我正在构建一个支持 flutter 和 firebase 的应用程序,用户可以在其中转到个人资料屏幕进行编辑。
个人资料有 2 部分,一个是查看部分,用户可以在其中查看其所有数据、验证其电子邮件并更改其个人资料图片,另一部分是编辑部分,用户可以更改电子邮件、姓名和个人简介。
这两个页面都包含一个侦听 AuthBloc 的块构建器。 Authbloc 有多个阶段,AuthLoading、Success、用户类的状态和 authstatus 等。问题是我需要始终具有最新用户信息的东西,因为如果用户首先单击“验证”或更改其个人资料图片编辑页面,导航到第二页时用户状态将丢失。
查看页面代码:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_application_1/core/common/roles/roles.dart';
import 'package:flutter_application_1/core/common/widgets/not_signed_in.dart';
import 'package:flutter_application_1/core/constants.dart';
import 'package:flutter_application_1/features/profile/pages/profile_screen_edit.dart';
import 'package:flutter_application_1/features/profile/widgets/delete_account.dart';
import 'package:flutter_application_1/features/profile/widgets/email_verified.dart';
import 'package:flutter_application_1/features/profile/widgets/profile_header.dart';
import 'package:flutter_application_1/features/profile/widgets/verify_email.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_application_1/features/auth/presentation/auth_bloc/auth_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';
class ProfileScreen extends StatefulWidget {
static String id = Constants.profile;
const ProfileScreen({super.key});
@override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
bool _showCheckEmailMessage = false;
@override
Widget build(BuildContext context) {
return SafeArea(
child: GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: PreferredSize(
preferredSize: const Size.fromHeight(60),
child: Center(
child: Container(
width: 600,
child: AppBar(
actions: [
BlocBuilder<AuthBloc, AuthState>(
buildWhen: (previous, current) {
if (current is AuthAppState) {
return current.status == AppStatus.authenticated;
}
return false;
}, builder: (context, state) {
if (state is AuthAppState &&
state.status == AppStatus.authenticated) {
return Column(
children: [
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ProfileEditScreen(
maxwidth: 500,
edit: (field, value) {
context.read<AuthBloc>().add(
AuthChangeField(
field, value));
},
)));
}),
],
);
} else {
return Container();
}
})
],
),
),
),
),
body: Padding(
padding: EdgeInsets.all(Constants.padding),
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 600),
child: BlocConsumer<AuthBloc, AuthState>(
listener: (context, state) {
if (state is AuthVerifyEmailMessage) {
setState(() {
_showCheckEmailMessage = true;
});
}
},
buildWhen: (previous, current) {
if (current is AuthAppState) {
return current.status == AppStatus.authenticated;
}
return false;
},
builder: (context, state) {
if (state is AuthAppState &&
state.status == AppStatus.authenticated) {
final user = state.user;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
// Profile Card
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
ProfileHeader(
profileImageUrl:
user.public.profileImageUrl,
editable: true,
radius: 60,
onDone: (image) {
context.read<AuthBloc>().add(
AuthChangeField("profileImageUrl",
base64.encode(image)));
},
),
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
user.public.name,
style: Theme.of(context)
.textTheme
.bodyLarge,
),
const SizedBox(height: 4),
Text(
user.public.bio,
style: Theme.of(context)
.textTheme
.bodyMedium,
),
const SizedBox(height: 4),
Text(
DateFormat.yMMMMEEEEd(
AppLocalizations.of(context)
?.localeName)
.format(user.readOnly.createdAt),
style: Theme.of(context)
.textTheme
.bodySmall,
),
const SizedBox(height: 4),
if (!(user.readOnly.roles.length == 1 &&
user.readOnly.roles.first ==
Role.user))
Text(
user.readOnly.roles
.map((role) =>
getLocalizedRoleName(
role, context))
.join(', '),
style: Theme.of(context)
.textTheme
.bodySmall,
),
],
),
],
),
],
),
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(
user.private.email,
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(width: 8),
_showCheckEmailMessage
? Text(
"Check email inbox",
style: Theme.of(context)
.textTheme
.bodyMedium
?.copyWith(color: Colors.green),
)
: user.private.emailVerified
? const EmailVerified()
: VerifyEmail(
onPressed: () => context
.read<AuthBloc>()
.add(const AuthVerifyEmail()),
),
],
),
),
if (user.private.phone != "")
Padding(
padding:
const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
user.private.phone!,
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(height: 20),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.primaryContainer,
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red),
onPressed: () => context
.read<AuthBloc>()
.add(const AuthLogout()),
child: Text(
AppLocalizations.of(context)!.logout),
),
DeleteAccount(
onPressed: () => context
.read<AuthBloc>()
.add(const AuthDelete()),
),
],
),
),
],
);
} else {
return const NotSignedIn();
}
},
),
),
),
),
),
),
);
}
}
编辑页面代码:
import 'package:flutter/material.dart';
import 'package:flutter_application_1/core/common/widgets/common_alert.dart';
import 'package:flutter_application_1/core/entities/user.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; // Add this import for bloc
import 'package:flutter_application_1/features/auth/presentation/auth_bloc/auth_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class ProfileEditScreen extends StatefulWidget {
final double maxwidth;
final Function edit; // Modify as necessary
const ProfileEditScreen({
Key? key,
required this.maxwidth,
required this.edit,
}) : super(key: key);
@override
_ProfileEditDialogState createState() => _ProfileEditDialogState();
}
class _ProfileEditDialogState extends State<ProfileEditScreen> {
final Map<String, TextEditingController> _controllers = {
"name": TextEditingController(),
"email": TextEditingController(),
"bio": TextEditingController(),
"phone": TextEditingController(),
};
String currentField = "name";
final Map<String, bool> _isEdited = {
"name": false,
"email": false,
"bio": false,
"phone": false,
};
final Map<String, bool> _isSaving = {
"name": false,
"email": false,
"bio": false,
"phone": false,
};
final Map<String, bool> _isSuccess = {
"name": false,
"email": false,
"bio": false,
"phone": false,
};
@override
void initState() {
super.initState();
//listentoAuthSuccess();
initializeControllers();
}
void initializeControllers() {
final authState = context.read<AuthBloc>().state;
if (authState is AuthAppState &&
authState.status == AppStatus.authenticated) {
_controllers["name"]?.text = authState.user.public.name;
_controllers["email"]?.text = authState.user.private.email;
_controllers["bio"]?.text = authState.user.public.bio;
}
}
@override
void dispose() {
_controllers.forEach((_, controller) => controller.dispose());
super.dispose();
}
// Initialize controllers with user data
void _initializeControllers(UserEntity user) {
_controllers["name"]?.text = user.public.name;
_controllers["email"]?.text = user.private.email;
_controllers["bio"]?.text = user.public.bio;
_controllers["phone"]?.text = user.private.phone ?? "";
}
@override
Widget build(BuildContext context) {
return BlocConsumer<AuthBloc, AuthState>(
buildWhen: (previous, current) {
if (current is AuthAppState) {
return current.status == AppStatus.authenticated;
}
return false;
},
listener: (context, state) {
print(state);
if (state is AuthAppState) {
_initializeControllers(state.user);
} else if (state is AuthLoading) {
// Start showing the loading state for the field being updated
setState(() {
_isSaving[currentField] = true;
});
} else if (state is AuthSuccess) {
// After success, show the check mark for 2 seconds
setState(() {
_isSaving.updateAll((key, value) => false);
_isSuccess[currentField] = true;
});
// Reset success state after 2 seconds
Future.delayed(Duration(seconds: 2), () {
setState(() {
_isSaving.updateAll((key, value) => false);
_isSuccess.updateAll((key, value) => false);
});
});
} else if (state is AuthFailure) {
setState(() {
_isSuccess.updateAll((key, value) => false);
_isSaving.updateAll((key, value) => false);
});
} else if (state is AuthVerifyEmailMessage) {
showMessage(
context,
AppLocalizations.of(context)!
.confirmation_email_sentTo(state.email),
AppLocalizations.of(context)!.confirm,
AppLocalizations.of(context)!.ok);
}
},
builder: (context, state) {
if (state is AuthAppState) {
// Initialize controllers with current user state
return Scaffold(
appBar: AppBar(),
backgroundColor: Colors.transparent,
body: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.all(16),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: widget.maxwidth),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
AppLocalizations.of(context)!.edit_profile,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 16),
// Display the current name
_buildEditableField(
"name",
AppLocalizations.of(context)!.name,
TextField(
controller: _controllers["name"],
onChanged: (value) {
setState(() {
currentField = "name";
_isEdited["name"] = true;
});
},
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.name,
hintStyle: Theme.of(context).textTheme.bodySmall,
labelStyle: Theme.of(context).textTheme.bodySmall,
),
),
() {
final updatedValue = _controllers["name"]?.text;
if (updatedValue != null &&
updatedValue.isNotEmpty) {
widget.edit("name", updatedValue);
}
},
),
const SizedBox(height: 8),
_buildEditableField(
"bio",
AppLocalizations.of(context)!.bio,
TextField(
controller: _controllers["bio"],
minLines: 4,
maxLines: 7,
onChanged: (value) {
setState(() {
currentField = "bio";
_isEdited["bio"] = true;
});
},
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.bio,
hintStyle: Theme.of(context).textTheme.bodySmall,
labelStyle: Theme.of(context).textTheme.bodySmall,
),
),
() {
final updatedValue = _controllers["bio"]?.text;
if (updatedValue != null &&
updatedValue.isNotEmpty) {
widget.edit("bio", updatedValue);
}
},
),
const SizedBox(height: 8),
_buildEditableField(
"email",
AppLocalizations.of(context)!.email,
TextField(
controller: _controllers["email"],
onChanged: (value) {
setState(() {
currentField = "email";
_isEdited["email"] = true;
});
},
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.email,
hintStyle: Theme.of(context).textTheme.bodySmall,
labelStyle: Theme.of(context).textTheme.bodySmall,
),
),
() {
final updatedValue = _controllers["email"]?.text;
if (updatedValue != null &&
updatedValue.isNotEmpty) {
widget.edit("email", updatedValue);
}
},
),
const SizedBox(height: 8),
const SizedBox(height: 16),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () => Navigator.of(context).pop(),
child: Text(AppLocalizations.of(context)!.close),
),
],
),
],
),
),
),
),
),
);
} else {
return SizedBox
.shrink(); // Return an empty widget when the state is not AuthAppState
}
},
);
}
Widget _buildEditableField(
String key,
String label,
Widget inputWidget,
VoidCallback onSave,
) {
return Row(
children: [
Expanded(child: inputWidget),
if (_isSaving[key] == true)
CircularProgressIndicator()
else if (_isSuccess[key] == true)
Icon(Icons.check, color: Colors.green)
else if (_isEdited[key] == true) // Show "Save" button only if edited
IconButton(
icon:
Icon(Icons.save, color: Theme.of(context).colorScheme.primary),
onPressed: () {
onSave();
setState(() {
_isEdited[key] = false;
});
},
),
],
);
}
}
AuthBloc 状态:
part of 'auth_bloc.dart';
enum AppStatus { authenticated, unauthenticated }
sealed class AuthState extends Equatable {
const AuthState();
@override
List<Object> get props => [];
}
class AuthAppState extends AuthState {
final AppStatus status;
final UserEntity user;
AuthAppState({UserEntity? user})
: user = user ?? UserEntity.empty,
status = (user?.id == "")
? AppStatus.unauthenticated
: AppStatus.authenticated;
@override
List<Object> get props => [status, user];
}
final class AuthVerifyEmailMessage extends AuthState {
final String email;
const AuthVerifyEmailMessage({required this.email});
}
final class AuthResetPasswordEmail extends AuthState {
final String email;
const AuthResetPasswordEmail({required this.email});
}
final class AuthLoading extends AuthState {}
final class AuthFailure extends AuthState {
final String message;
const AuthFailure({required this.message});
}
final class AuthSuccess extends AuthState {}
处理既需要用户信息又需要其他状态的页面的最佳方法是什么?
通常在这样的情况下,有 2 种最好的方法可以处理这个问题
您应该将用户个人资料信息上传到数据库,并使用简单的未来构建器在每次查看该信息时获取最新信息。然后,您可以使用获取的信息将其传递给状态提供程序类,以便在编辑这些值时使用最新值初始化这些字段。
现在每次向 Firebase 发送请求只是为了查看用户信息并不理想。因此,您还可以使用共享首选项或SQLite实现简单的缓存机制,以在本地保存这些值并在更新用户信息时立即更新缓存。使用本地保存的值来初始化您在查看查看配置文件页面时正在处理的值的状态。您可以在您正在查看的页面的
void initState()
中设置值。