I'm just started with flutter bloc. I want to make a movie listing app, create your own lists and share your friends etc.
The problem is, when i tapped to logout button, UI does not update.
Here's where i try to change the UI. If the state is Authenticated(), I'm returning WatchlistNavBar(), if the state is Unauthenticated() I'm returning WelcomeView() for login or register.
If the user has submitted email and password correctly, WatchlistNavBar() is building. Everything works fine. But when the user tries the logout, WelcomeView() does not build.
By the way BlocNavigate() class is called in MaterialApp()'s home property.
class BlocNavigate extends StatelessWidget {
const BlocNavigate({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Loading) {
return const LoadingWidget();
} else if (state is Authenticated) {
return const WatchlistNavBar();
} else if (state is Unauthenticated) {
return const WelcomeView();
} else {
return const SignInView();
}
},
);
}
}
AuthBloc:
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthRepository authRepository = AuthRepository();
AuthBloc(this.authRepository) : super(AuthInitial()) {
on<AuthenticationStarted>(_onAuthStarted);
on<AuthenticationSignedOut>(_onSignOut);
}
_onAuthStarted(AuthenticationStarted event, Emitter<AuthState> emit) async {
UserModel user = await authRepository.getCurrentUser().first;
if (user.uid != "uid") {
emit(Authenticated());
} else {
emit(Unauthenticated());
}
}
_onSignOut(AuthenticationSignedOut event, Emitter<AuthState> emit) async {
authRepository.signOut();
emit(Unauthenticated());
}
}
AuthState:
abstract class AuthState extends Equatable {
const AuthState();
#override
List<Object> get props => [];
}
class AuthInitial extends AuthState {}
class Authenticated extends AuthState {}
class Unauthenticated extends AuthState {}
class Loading extends AuthState {}
And this is the logout button, where i add AuthenticationSignedOut() to AuthBloc():
class LogoutButton extends StatelessWidget {
const LogoutButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.exit_to_app, color: Colors.black),
onPressed: () {
context.read<AuthBloc>().add(AuthenticationSignedOut());
});
}
}
My main function and MaterialApp():
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
Bloc.observer = AppBlocObserver();
runApp(
MultiBlocProvider(providers: [
BlocProvider(create: (context) => FormBloc()),
BlocProvider(create: (context) => DatabaseBloc(DatabaseRepositoryImpl())),
BlocProvider(
create: (context) =>
AuthBloc(AuthRepository())..add(const AuthenticationStarted())),
BlocProvider(
create: (context) => FavoritesBloc()..add(const FavoritesLoad()))
], child: const WatchlistApp()),
);
}
class WatchlistApp extends StatelessWidget {
const WatchlistApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Watchlist',
theme: WatchlistTheme.mainTheme,
home: const BlocNavigate(),
);
}
}
As i said, i'm new to flutter bloc and don't know exactly what I'm doing wrong. If you need more information please let me know.
Related
I am looking for the right way of implementing streams using bloc. I have a appUser of type AppUser which changes based on the stream . I want to listen to this appUser throughout the application using bloc.
My present implementation .
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(lazy: false, create: (context) => UserCubit())
],
child: MaterialApp(
theme: customTheme(context),
home: const MyWidget(),
));
}
UserCubit.dart
class UserCubit extends Cubit<UserState> {
final _firestore = FirebaseFirestore.instance;
final User? _currentUser = FirebaseAuth.instance.currentUser;
AppUser? appUser;
UserCubit() : super(UserInitialState()) {
emit(UserMainLoadingState());
_firestore
.collection("users")
.doc(_currentUser?.uid) // or whatever your collection name is
.snapshots()
.listen((event) {
event.exists
? {
print(" The user is ${event.data()}"),
appUser = AppUser.fromjson(event.data()!, event.id), // here it changes
emit(UserExists(user: appUser!))
}
: {print("Oops no user"), emit(UserNotExists())};
});
}
MyWidget.dart
class MyWidget extends StatelessWidget {
const MyWidget({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(
child: BlocBuilder(builder: (context, state) {
return Text(BlocProvider.of<UserCubit>(context).appUser.toString()); // I want to get the changes of appUser
})));
}
}
You should move your AppUser to the UserState like this:
abstract class UserState extends Equatable {
final AppUser? appUser;
const UserState({this.appUser});
#override
List<Object?> get props => [appUser];
}
class UserExists extends UserState {
const UserExists({required AppUser user}) : super(appUser: user);
}
And then you can read it from state of your BlocBuilder like so:
BlocBuilder<UserCubit, UserState>(
builder: (context, state) {
return Text(state.appUser.toString());
},
);
The problem is that I would like to show a loading indicator when the user tries to fetch some data from an api. But when the user presses the button, loading indicator shows once. But I would like to show the loading indicator every time when the user tries to fetch. It works but as I say It works once. Could anyone have any idea what can cause this problem? Here's the minimal code to reproduce the issue:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(create: (_) => HomeCubit()),
],
child: const MaterialApp(
title: 'Flutter Bloc Demo',
home: HomeView(),
),
);
}
}
class HomeView extends BaseView<HomeCubit, HomeState> {
const HomeView({Key? key}) : super(key: key);
#override
Widget builder(HomeCubit cubit, HomeState state) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(state.count.toString()),
ElevatedButton(
onPressed: cubit.increment,
child: const Text('Increase'),
),
],
),
);
}
}
class HomeState extends BaseState {
final int count;
HomeState({required this.count});
HomeState copyWith({
int? count,
}) {
return HomeState(
count: count ?? this.count,
);
}
}
class HomeCubit extends BaseCubit<HomeState> {
HomeCubit() : super(HomeState(count: 0));
void increment() {
flow(() async {
await Future.delayed(const Duration(seconds: 1));
emit(state.copyWith(count: state.count + 1));
});
}
}
#immutable
abstract class BaseView<C extends StateStreamable<S>, S extends BaseState>
extends StatelessWidget {
const BaseView({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) {
return BaseCubit(context.read<S>());
},
child: Scaffold(
body: BlocBuilder<C, S>(
builder: (context, state) {
final cubit = context.read<C>();
if (state.loadingState == LoadingState.loading) {
return loadingWidget;
}
return builder.call(cubit, state);
},
),
),
);
}
Widget builder(C cubit, S state);
Widget get loadingWidget => const Center(
child: CircularProgressIndicator(),
);
}
enum LoadingState { initial, loading, loaded }
class BaseState {
LoadingState loadingState;
BaseState({
this.loadingState = LoadingState.initial,
});
}
class BaseCubit<S extends BaseState> extends Cubit<S> {
BaseCubit(S state) : super(state);
Future<void> flow(Future<void> Function() function) async {
state.loadingState = LoadingState.loading;
emit(state);
await function();
state.loadingState = LoadingState.loaded;
emit(state);
}
}
Is it overengineering? I don't think you are duplicating much code if you just use BlocBuilder instead of some base class.
If bloc already exist you should provide it by BlocProvider.value instead of BlocProvider(create: read())
You should use context.watch instead of context.read to get a new value every time the state changes. context.read receives state only once.
It's overengineering, please take a look at https://bloclibrary.dev/#/coreconcepts. There are enough tutorials to catch the basic idea.
Then try to use bloc + freezed. Here is an example https://dev.to/ptrbrynt/why-bloc-freezed-is-a-match-made-in-heaven-29ai
I am using Bloc to check internet connection. If there is no connection, I show the SnackBar. But I also need to be able to reuse the connection method to re-check the connection by clicking on the button, but I don’t understand how to call this method. Tell me how to call the connection method when the button is clicked?
bloc
class ConnectedBloc extends Bloc<ConnectedEvent, ConnectedState> {
StreamSubscription? subscription;
ConnectedBloc() : super(ConnectedInitial()) {
on<OnConnectedEvent>((event, emit) => emit(ConnectedSucess()));
on<OnNotConnectedEvent>((event, emit) => emit(ConnectedFailure()));
void connection() => Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
if (result == ConnectivityResult.wifi ||
result == ConnectivityResult.mobile) {
add(OnConnectedEvent());
} else {
add(OnNotConnectedEvent());
}
});
home
home: BlocConsumer<ConnectedBloc, ConnectedState>(
listener: ((context, state) {
if (state is ConnectedSucess) {
const SizedBox();
} else if (state is ConnectedFailure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: const Duration(seconds: 3),
backgroundColor: Colors.transparent,
elevation: 0,
content: SystemMessagesSnackBar(
message: 'No internet access. Check your connection',
textButton: 'Refresh',
onPressed: () =>
ScaffoldMessenger.of(context).hideCurrentSnackBar(),
icon: SvgPicture.asset(constants.Assets.no_connection),
),
),
);
}
}),
To call a method inside your bloc, you need to get a reference to your bloc first by using context.read<T>(). In your case:
context.read<ConnectedBloc>()
You can then call the method as follows:
onPressed:(){
ScaffoldMessenger.of(context).hideCurrentSnackBar();
context.read<ConnectedBloc>().connection();
}
But this will create an additional stream. Maybe you should use checkConnectivity if you click on the button.
wrap the code with a streambuilder and then associate the bloc streams with the streambuilder
in your button click event interact with the bloc class
check the streambuilder state and process the returning data.
yaml
dependencies:
flutter:
sdk: flutter
equatable: ^2.0.3
rxdart: ^0.27.4
import 'package:flutter/material.dart';
import 'package:equatable/equatable.dart';
import 'package:rxdart/rxdart.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Button Stream',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Test_StreambuilderButton(),
);
}
}
class Test_StreambuilderButton extends StatefulWidget {
Test_StreambuilderButton({Key? key}) : super(key: key);
BlocCode bloc= new BlocCode();
#override
State<Test_StreambuilderButton> createState() => _Test_StreambuilderButtonState();
}
class _Test_StreambuilderButtonState extends State<Test_StreambuilderButton> {
#override
Widget build(BuildContext context) {
return
Scaffold(appBar: AppBar(title:Text("Button Stream Event")),
body:
Column(children: [
StreamBuilder<BlocState>(
stream: widget.bloc.blocStream,
builder:(context,snapshot)
{
if (snapshot.hasData)
{
String data=snapshot.data!._message;
if (data == null)
{
return (Container(child:Text("No data")));
}
return (Container(child:Text(data)));
}
else
{
return (Container(child:Text("No activity")));
}
}
),
ElevatedButton(onPressed:(){
widget.bloc.setMessage(BlocState("Hello World"));
}, child: Text("Press Me"))
]));
}
}
class BlocState extends Equatable
{
BlocState(this._message);
final String _message;
#override
List<Object> get props=>[_message];
String get getMessage {return _message;}
}
class BlocCode
{
BlocCode();
Stream<BlocState> get blocStream => _loadController.stream;
final _loadController=BehaviorSubject<BlocState>();
void dispose()
{
_loadController.close();
}
setMessage(BlocState state)
{
_loadController.sink.add(state);
}
}
I've just started with Flutter recently. BLoC does indeed has a steep learning curve...
As it is clear from the title, the BlocBuilder logic gets correctly executed only once, when the app is started. Afterwards, however, UI doesn't get rebuilt on state changes. Although all the events get emitted, and states change.
Thanks in advance for any help!
Here's my 'main.dart':
import 'package:co_flutter/auth/authentication_bloc.dart';
import 'package:co_flutter/auth/authentication_event.dart';
import 'package:co_flutter/auth/authentication_state.dart';
import 'package:co_flutter/auth/login/login_bloc.dart';
import 'package:co_flutter/auth/signup/signup_page.dart';
import 'package:co_flutter/loading_indicator.dart';
import 'package:co_flutter/splash_page.dart';
import 'package:co_flutter/user_repository.dart';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'auth/login/login_page.dart';
class SimpleBlocObserver extends BlocObserver {
#override
void onCreate(BlocBase bloc) {
super.onCreate(bloc);
print('onCreate -- ${bloc.runtimeType}');
}
#override
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
print('onEvent -- ${bloc.runtimeType}, $event');
}
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print('onTransition -- ${bloc.runtimeType}, $transition');
}
#override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('onChange -- ${bloc.runtimeType}, $change');
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('onError -- ${bloc.runtimeType}, $error');
super.onError(bloc, error, stackTrace);
}
#override
void onClose(BlocBase bloc) {
super.onClose(bloc);
print('onClose -- ${bloc.runtimeType}');
}
}
void main() {
Bloc.observer = SimpleBlocObserver();
runApp(MyApp(
userRepository: UserRepository(),
));
}
class MyApp extends StatefulWidget {
final UserRepository userRepository;
MyApp({Key? key, required this.userRepository}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late AuthenticationBloc authenticationBloc;
UserRepository get userRepository => widget.userRepository;
#override
void initState() {
authenticationBloc = AuthenticationBloc(userRepository: userRepository);
authenticationBloc.add(AppStarted());
super.initState();
}
#override
void dispose() {
authenticationBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
create: (BuildContext context) => authenticationBloc,
),
BlocProvider<LoginBloc>(
create: (BuildContext context) => LoginBloc(
userRepository: userRepository,
authenticationBloc: authenticationBloc),
),
],
child: MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (BuildContext context, AuthenticationState state) {
if (state is AuthenticationUninitialized) {
return SplashPage();
}
if (state is AuthenticationAuthenticated) {
return Dashboard(
title: 'Dashboard',
);
}
if (state is AuthenticationUnauthenticated) {
return LoginPage(
userRepository: userRepository,
);
}
if (state is AuthenticationLoading) {
return LoadingIndicator();
}
// else {
// return Text('Error');
// }
return BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
if (state is LoginToSignup) {
return SignUpPage();
} else
return SizedBox.shrink();
},
);
},
),
),
);
}
}
class Dashboard extends StatelessWidget {
final String title;
const Dashboard({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
final AuthenticationBloc authenticationBloc =
BlocProvider.of<AuthenticationBloc>(context);
return Scaffold(
appBar: AppBar(
title: Text('Dashboard'),
),
body: Container(
child: Center(
child: ElevatedButton(
child: Text('logout'),
onPressed: () {
authenticationBloc.add(LoggedOut());
},
),
),
),
);
}
}
authentication_bloc
import 'dart:async';
import 'package:co_flutter/auth/authentication_event.dart';
import 'package:co_flutter/auth/authentication_state.dart';
import 'package:bloc/bloc.dart';
import '../user_repository.dart';
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
UserRepository userRepository;
AuthenticationBloc({required this.userRepository})
: super(AuthenticationUninitialized()) {
userRepository = UserRepository();
}
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AppStarted) {
final bool hasToken = await userRepository.hasToken();
if (hasToken) {
yield AuthenticationAuthenticated();
} else {
yield AuthenticationUnauthenticated();
}
}
if (event is LoggedIn) {
yield AuthenticationLoading();
await userRepository.persistToken(event.token, event.userId);
yield AuthenticationAuthenticated();
}
if (event is LoggedOut) {
yield AuthenticationLoading();
await userRepository.deleteToken();
yield AuthenticationUnauthenticated();
}
}
}
authentication_event
import 'package:equatable/equatable.dart';
abstract class AuthenticationEvent extends Equatable {
#override
List<Object> get props => [];
}
class AppStarted extends AuthenticationEvent {
#override
String toString() => 'AppStarted';
}
class LoggedIn extends AuthenticationEvent {
final String token;
final String userId;
LoggedIn({required this.token, required this.userId});
#override
String toString() => 'LoggedIn { token: $token}';
}
class LoggedOut extends AuthenticationEvent {
#override
String toString() => 'LoggedOut';
}
authentication_state
import 'package:equatable/equatable.dart';
abstract class AuthenticationState extends Equatable {
const AuthenticationState();
}
class AuthenticationUninitialized extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationLoading extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationAuthenticated extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationUnauthenticated extends AuthenticationState {
#override
List<Object> get props => [];
}
The problem is here. You already created a bloc:
authenticationBloc = AuthenticationBloc(userRepository: userRepository);
And here you are trying to create it again.
To fix it replace this code:
BlocProvider<AuthenticationBloc>(
create: (BuildContext context) => authenticationBloc,
),
with this:
BlocProvider<AuthenticationBloc>.value(
value: authenticationBloc,
),
Pass bloc into bloc builder
BlocBuilder<AuthenticationBloc, AuthenticationState>(
bloc: authenticationBloc,
Don't forget to dispose bloc authenticationBloc after you used it in dispose function.
I'm using flutter to make a mobile app.
In app.dart, the code controls the main routing.
class App extends StatelessWidget {
const App({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: hexToColor("#6a1717")),
home: BlocBuilder<AuthenticationBloc, AuthenticationDataState>(
builder: (context, state) {
if (state.state == AuthenticationState.uninitialized) {
return SplashPage();
} else if (state.state == AuthenticationState.authenticated) {
print("now home page");
return HomePage();
} else if (state.state == AuthenticationState.unauthenticated) {
return LoginPage();
} else if (state.isLoading) {
return LoadingIndicator();
} else {
return null;
}
},
),
);
}
}
And in login bloc handler, yielded logged_in event after log in.
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final AuthenticationBloc authenticationBloc;
LoginBloc({
#required this.authenticationBloc,
}) : assert(authenticationBloc != null),
super(LoginInitial());
#override
Stream<LoginState> mapEventToState(LoginEvent event) async* {
if (event is LoginButtonPressed){
yield LoginLoading();
try {
final user = await userController.authenticate(
email: event.email,
password: event.password
);
if(user != null){
if(user.accessToken != null){
authenticationBloc.add(LoggedIn(user: user));
yield LoginInitial();
}else{
yield NotValid();
}
}else{
yield NotRegistered();
}
}catch(error){
yield LoginFailure(error: AppException.unknown(message: error.toString()));
}
}
}
}
When debugging, I checked out that the command "print("now home page");" is executed, and the processor went in home page actually but the login screen does not disapper and the home page is not rendered.
I don't know what's going on in flutter. Please help me.
Login page:
class LoginPage extends StatelessWidget {
const LoginPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
// resizeToAvoidBottomInset: false,
body: BlocProvider(
create: (context) {
return LoginBloc(
authenticationBloc: BlocProvider.of<AuthenticationBloc>(context),
);
},
child: LoginForm(),
),
);
}
}
Home Page:
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
...
}
}