I am new to blocs in Flutter and I am trying to use them to create, read, edit, and store user inputted data. I am currently using a bloc for my app's onboarding process. Ideally, once the user creates an account using Firebase email & password authentication, they will be prompted to answer a list of questions. The problem is, my state does not change from loading to loaded. I have it set up to display a circular progress bar until it is loaded, then the text will appear, but there is no state change. I could really use some guidance or insight please :).
Note I’ve watched several videos and have had no luck, and I really need some help/guidance
Main dart:
int? isViewed;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await FirebaseAppCheck.instance.activate();
final prefs = await SharedPreferences.getInstance();
final showLogin = prefs.getBool('showLogin') ?? false;
Paint.enableDithering = true;
// This is for our onboarding screen
isViewed = prefs.getInt('onboard');
runApp(MyApp(showLogin: showLogin));
}
class MyApp extends StatelessWidget {
final bool showLogin;
const MyApp({Key? key,
required this.showLogin}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiRepositoryProvider(
providers: [
RepositoryProvider(
create: (context) => DatabaseRepository()
),
RepositoryProvider(
create: (context) => StorageRepository()
)
],
child: MultiBlocProvider(
providers: [
BlocProvider<OnboardingBloc>(
create: (context) => OnboardingBloc(
databaseRepository: context.read<DatabaseRepository>(),
storageRepository: context.read<StorageRepository>()
))
],
child: MaterialApp(
title: 'Strength',
debugShowCheckedModeBanner: false,
initialRoute: AccountOnboarding.routeName, // Splash Screen
routes: {
'/splash screen' : (context) => const SplashScreen(),
'/main onboarding' : (context) => const OnboardingScreen(),
'/landing' : (context) => LandingScreen(),
'/login' : (context) => const LoginScreen2(),
'/dashboard' : (context) => const Dashboard(),
'/profile onboarding' : (context) => const AccountOnboarding()
},
home: AccountOnboarding() // FINAL SCREEN IS SPLASH SCREEN
)),
);
}
}
Onboarding Bloc:
part 'onboarding_event.dart';
part 'onboarding_state.dart';
class OnboardingBloc extends Bloc<OnboardingEvent, OnboardingState> {
final DatabaseRepository _databaseRepository;
final StorageRepository _storageRepository;
OnboardingBloc({
required DatabaseRepository databaseRepository,
required StorageRepository storageRepository,
}) :
_databaseRepository = databaseRepository,
_storageRepository = storageRepository,
super(OnboardingLoading()) {
on<StartOnboarding>(_onStartOnboarding);
on<UpdateUser>(_onUpdateUser);
on<UpdateUserImage>(_onUpdateUserImage);
}
void _onStartOnboarding(
StartOnboarding event,
Emitter<OnboardingState> emit)
async {
await _databaseRepository.createUser(event.user);
emit(OnboardingLoaded(user: event.user));
}
void _onUpdateUser(
UpdateUser event,
Emitter<OnboardingState> emit) {
if (state is OnboardingLoaded) {
_databaseRepository.UpdateUser(event.user);
emit(OnboardingLoaded(user: event.user));
}
}
void _onUpdateUserImage(
UpdateUserImage event,
Emitter<OnboardingState> emit)
async{
if (state is OnboardingLoaded) {
User user = (state as OnboardingLoaded).user;
await _storageRepository.uploadImage(user, event.image);
_databaseRepository.getUser(user.id!).listen((user) {
add(UpdateUser(user: user));
});
}
}
}
Onboarding States:
part of 'onboarding_bloc.dart';
abstract class OnboardingState extends Equatable {
const OnboardingState();
@override
List<Object> get props => [];
}
class OnboardingLoading extends OnboardingState {}
class OnboardingLoaded extends OnboardingState {
final User user;
OnboardingLoaded({required this.user});
@override
List<Object> get props => [user];
}
Onboarding Screen format:
class AccountOnboarding extends StatelessWidget {
static const String routeName = '/profile onboarding';
static Route route() {
return MaterialPageRoute(
settings: RouteSettings(name: routeName),
builder: (context) => MultiBlocProvider(
providers: [
BlocProvider<OnboardingBloc>
(create: (_) => OnboardingBloc(
databaseRepository: context.read<DatabaseRepository>(),
storageRepository: context.read<StorageRepository>(),
)..add(StartOnboarding())
)
],
child: AccountOnboarding(),
));
}
static const List<Tab> tabs = <Tab>[
Tab(text: 'Name'),
Tab(text: 'Age and Profile'),
Tab(text: 'Bio and Interests'),
Tab(text: 'Selection')
];
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: tabs.length,
child: Builder(builder: (BuildContext context) {
final TabController tabController = DefaultTabController.of(context)!;
tabController.addListener(() {
if (!tabController.indexIsChanging) {}
});
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: const Color(0xff31708c),
appBar: AppBar(
automaticallyImplyLeading: false,
backgroundColor: Colors.transparent,
elevation: 0,
title: Row(
children: [
Expanded(
child: Image.asset('assets/images/Logo_Strength.png',
height: 50),
),
Expanded(
flex: 2,
child: RichText(
text: TextSpan(
style: GoogleFonts.montserrat(
fontSize: 30),
children: <TextSpan> [
TextSpan(text: 'Stren',
style: GoogleFonts.montserrat(
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.7),
offset: const Offset(1.5, 0.0))
])),
TextSpan(text: ';',
style: GoogleFonts.montserrat(
color: const Color(0xffef6a7a), fontWeight: FontWeight.bold,
letterSpacing: 1,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.7),
offset: const Offset(1.5, 0.0))
])),
TextSpan(text: 'th',
style: GoogleFonts.montserrat(
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1,
shadows: [
Shadow(
color: Colors.black.withOpacity(0.7),
offset: const Offset(1.5, 0.0))
]))
],
),
),
),
],
)
),
body: TabBarView(
// physics: const NeverScrollableScrollPhysics(),
children: [
NamePage(tabController: tabController,),
ageAndPicture(tabController: tabController,),
bioAndInterests(tabController: tabController,),
SelectionPage(tabController: tabController,)
],
));
}
));}}
First onboarding screen (where states are addressed):
class NamePage extends StatelessWidget {
final TabController tabController;
const NamePage({Key? key,
required this.tabController})
: super(key: key);
@override
Widget build(BuildContext context) {
double _height = MediaQuery.of(context).size.height;
return BlocBuilder<OnboardingBloc, OnboardingState>(
builder: (context, state) {
if (state is OnboardingLoading) {
return Center(
child: CircularProgressIndicator(),
);
}
if (state is OnboardingLoaded) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: const Color(0xff31708c),
body: Padding(
padding: EdgeInsets.only(
left: 30,
right: 30,
top: _height * 0.055,
bottom: _height * 0.05),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
children: [
Column(
children: <Widget>[
Text('Here at Strength, we respect your preferences. How would you like to be addressed?',
style: GoogleFonts.montserrat(
color: Colors.white,
fontSize: 19,
fontWeight: FontWeight.w600
),
textAlign: TextAlign.center,),
Padding(
padding: EdgeInsets.only(
top: _height * 0.22),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextField(
maxLength: 14,
maxLengthEnforcement: MaxLengthEnforcement.enforced,
onChanged: (value) {
context.read<OnboardingBloc>()
.add(UpdateUser(
user: state.user.copyWith(name: value)));
},
cursorColor: Colors.white,
style: GoogleFonts.montserrat(
color: Colors.white,
fontSize: 19,
height: 2
),
decoration: InputDecoration(
enabledBorder: InputBorder.none,
disabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
filled: true,
helperText: 'Your name',
helperStyle: GoogleFonts.montserrat(
color: Colors.white,
fontSize: 14.5,
fontWeight: FontWeight.w600
),
counterStyle: GoogleFonts.montserrat(
fontSize: 14.5,
fontWeight: FontWeight.w600
),
// counterText: "",
labelStyle:
GoogleFonts.montserrat(
color: Colors.white,
fontSize: 18),
hintText: 'Please call me . . .',
hintStyle: GoogleFonts.montserrat(
color: Colors.white54,
fontWeight: FontWeight.w600,
fontSize: 18),
border: InputBorder.none),
),
],
),
),),
],
),
],
),
Column(
children: [
Align(
alignment:
Alignment.topLeft,
child: Text('1/4',
style: GoogleFonts.montserrat(
color: Colors.white,
fontWeight: FontWeight.w500
),)),
const SizedBox(height: 5,),
const StepProgressIndicator(
totalSteps: 4,
currentStep: 1,
roundedEdges: Radius.circular(20),
size: 3,
padding: 3,
selectedColor: Colors.white,
unselectedColor: Color.fromARGB(255, 20, 83, 106),),
const SizedBox(height: 13,),
CustomButton(tabController: tabController)
],
),
],
),
),
);
}
else {
return const Text('Something went wrong.');
}
}
);
}
}
Update: I was able to get it to work.
I ended up creating blocs for first authenticating the new user with email and password signup, then the new account triggered a state change for firestore.