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);
}
}
Related
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.
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 have a Provider model such as
provider_model.dart:
import 'package:flutter/material.dart';
class ProviderModel extends ChangeNotifier {
final List<String> _myList = [];
List<String> get myList => [..._myList];
void addItem(String item) {
_myList.add(item);
notifyListeners();
}
}
Now, Flutter documentation shows us how to listen to websockets. Here I am using their example together with my ProviderModel():
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import './provider_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (BuildContext context) => ProviderModel(),
child: Scaffold(
body: MyHomePage(),
)),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _controller = TextEditingController();
final _channel = WebSocketChannel.connect(
Uri.parse('wss://echo.websocket.events'),
);
#override
Widget build(BuildContext context) {
return Consumer<ProviderModel>(
builder: (context, provider, _) {
return Column(children: [
TextField(
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter a search term',
suffixIcon: IconButton(
icon: Icon(
Icons.send,
),
onPressed: _sendMessage)),
),
...provider.myList.map((e) => Text(e)).toList(),
StreamBuilder(
stream: _channel.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
// ERROR HERE!
provider.addItem('I was added');
return Text("Item added");
} else if (snapshot.hasError) {
return Text(snapshot.error as String);
} else {
return CircularProgressIndicator();
}
},
)
]);
},
);
}
void _sendMessage() {
if (_controller.text.isNotEmpty) {
_channel.sink.add(_controller.text);
print('done');
}
}
}
Which the following output (Chrome):
Now, when I click on the send button, it calls _sendMessage() (code above). And then since the StreamBuilder() hasData it runs this line (code above):
provider.addItem('I was added');
However, this is where my error appears, I am getting the following error:
The following assertion was thrown while dispatching notifications for ProviderModel:
setState() or markNeedsBuild() called during build.
Why am I getting this error? Where is the widget rebuilding? See this answer to Flutter Provider setState() or markNeedsBuild() called during build
You can take help from addPostFrameCallback, but the cost is it will keep rebuilding on every frame, to control this behavior you can use a bool on state class.
bool isDone = false;
void addItemH() {
if (!isDone) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<ProviderModel>(context, listen: false)
.addItem('I was added');
isDone = true;
});
}
}
And builder
builder: (context, snapshot) {
if (snapshot.hasData) {
addItemH();
return Text("Item added");
And to control the next insertaion.
void _sendMessage() {
if (_controller.text.isNotEmpty) {
isDone = false;
_channel.sink.add(_controller.text);
print('done');
}
}
}
If you just want to add data only single time, it is better to do inside inside initState.
I am developing a Flutter app where it acts as a client, connecting to a server via an API.
The app makes requests and depending on the response it progresses the state.
My question is the following: Can I make a request, and then depending on the response, either update the UI or open a new page?
I have used FutureBuilder as shown below. The problem is that the FutureBuilder requires to return a UI. In my case, if the response is OK I want to open a new page (see //todo line).
I tried using Navigator.pushReplacement but it does not really work.
Any ideas?
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/rendering.dart';
import 'model.dart';
class Start extends StatefulWidget {
final String title;
Start({Key key, #required this.title}) : super(key: key);
#override
State<StatefulWidget> createState() => new StartState();
}
class StartState extends State<Start> {
Future<StartReply> _startReply;
_makeRequest() {
setState(() {
_startReply = ...; // actual API request
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: widget.title,
home: Scaffold(
appBar: AppBar(
title: Text(widget.title),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(false)
),
),
body: Center(
child: FutureBuilder(
future: _startReply,
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.none) {
return ElevatedButton(
onPressed: _makeRequest,
child: Text("Make request")
);
} else if(snapshot.connectionState == ConnectionState.done && snapshot.hasData) {
// todo open page here
return Text('Started!', style: TextStyle(color: Colors.green, fontStyle: FontStyle.italic));
} else if(snapshot.hasError) {
debugPrint('StartReply: ${snapshot.data}');
return Text('Error (${snapshot.error})', style: TextStyle(color: Colors.red, fontStyle: FontStyle.italic));
} else {
return CircularProgressIndicator();
}
}
)
)
)
);
}
}
Yes, you should not use a FutureBuilder if you want to do anything other than changing the UI depending on the async task. You should manage your own async. Here's some code to get you started:
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool loaded;
#override
void initState() {
super.initState();
asyncInit();
}
Future<void> asyncInit() async {
final response =
await doTheNetworkRequest() //imagine that this was an http request
if (yes) {
setState(() {
loaded = true;
});
} else {
Navigator.of(context).push(...);
}
}
#override
Widget build(BuildContext context) {
return loaded == true ? Text('Loaded') : Text('Loading');
}
}
I have a raised button that kicks off my fingerprint authentication, when the Future returns I want to be able to change the Raised Button to new text and new onPressed method to complete the required authentication. I have given the Raised Button a key but can not find how to act upon that button to change it. Is it possible? Anyone have examples of it?
I tried to create new Raised Button with same key based on if the user is authenticated, but it did not change anything.
Any help would be great.
I would recommend reviewing the Flutter Interactivity Tutorial.
Once the Future completes you can call setState to tell Flutter to rebuild your StatefulWidget. And in your build() method, you can use the authenticated status of the user to construct a different RaisedButton.
Here's some example code that does this:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Local Auth Demo',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _authenticated = false;
Future<Null> _authenticate() async {
final LocalAuthentication auth = new LocalAuthentication();
bool authenticated = false;
try {
authenticated = await auth.authenticateWithBiometrics(
localizedReason: 'Scan your fingerprint to authenticate',
useErrorDialogs: true);
} on PlatformException catch (e) {
print(e);
}
if (!mounted) return;
setState(() {
_authenticated = authenticated;
});
}
Widget _buildAuthButton() {
assert(!_authenticated);
return new RaisedButton(
child: new Text('Authenticate'),
onPressed: _authenticate,
);
}
Widget _buildContinueButton() {
assert(_authenticated);
return new RaisedButton(
child: new Text('Continue'),
onPressed: () {
// Do something now that the user is authenticated
},
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Interactivity Tutoral'),
),
body: new Center(
child: _authenticated ? _buildContinueButton() : _buildAuthButton(),
),
);
}
}
I would use FutureBuilder, just return one widget or the other based on whether the Future is complete
new FutureBuilder<String>(
future: your_future,
builder: (_, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return new Text(
'Your data: ${snapshot.data}',
);
}
})