Does buildSuggestions of showSearch method run continuously? - dart

I'm new in flutter and I have a function to search working with API,and i have created a function called _fetchData() inside StatefulWidget:
_fetchData() async {
print("fetchData Run");
print(widget.query);
this.setState((){
isLoading=true;
});
final response = await http.get(URL+'search/keyword?api_key='+API_KEY+'&query='+widget.query);
if (response.statusCode == 200) {
print(response.body);
var list = json.decode(response.body)["results"] as List;
keywordList = list.map<Keyword>((json)=>Keyword.fromJson(json)).toList();
this.setState((){
isLoading=false;
});
} else {
throw Exception('Failed to load photos');
}
}
and i put my StatefulWidget in buildSuggestions like:
class SearchHome extends SearchDelegate {
#override
List<Widget> buildActions(BuildContext context) {
//widget
}
#override
Widget buildLeading(BuildContext context) {
//widget
}
#override
Widget buildResults(BuildContext context) {
//widget
}
#override
Widget buildSuggestions(BuildContext context) {
//SuggestKeyword is my statefullwidget with _fetchData() function inside
return SuggestKeyword(query: query);
}
}
but i'm confuse why _fetchData() keep running continuously, that's mean my apps keep hit an API, i think it's not good so i want to avoid it
I know i can put SuggestKeyword inside buildResults, but these method need user to submit/enter the keyboard to run, i want to search while the user is still typing, but buildSuggestions keep running even when user is not typing,
am i doing something wrong? any suggestion would appreciated!

Related

Flutter integrating Hive database with Riverpod

There is very easy way to use Hive key-value database on StatefulWidgets, for example:
class HookDemo extends StatefulWidget {
#override
_HookDemoState createState() => _HookDemoState();
}
class _HookDemoState extends State<HookDemo> {
Box user;
#override
void initState() {
super.initState();
user = Hive.box<User>('user');
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
final _u = User()
..nameFamily = 'myname'
..mobileNumber = '123456789';
_user!.add(_u);
_u.save();
},
child: Icon(Icons.add),
),
...
);
}
}
here we defined Box user property and inside initState we implemented what's user such as user = Hive.box<User>('user');
after that we can use user without any problem and getting already opened error
now in this current application we used HookWidget and when we want to use Hive we get error as box already opened
main.dart:
Future<void> initHiveDriver() async {
final appDocumentDirectory = await path_provider.getApplicationDocumentsDirectory();
await Hive.initFlutter(appDocumentDirectory.path);
await Hive.openBox<UserAdapter>('user');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
///...
initHiveDriver();
runApp(
ProviderScope(observers: [
Logger()
],
child: MyApp()),
);
}
how can i create a provider for Hive with Riverpod and use it into HookWidget?
I am using Hive with Riverpod like this.
I am using a named constructor so I can await the openBox call.
final hiveProvider = FutureProvider<HiveDB>((_) => HiveDB.create());
class HiveDB {
var _userBox;
HiveDB._create() {}
static Future<HiveDB> create() async {
final component = HiveDB._create();
await component._init();
return component;
}
_init() async {
Hive.registerAdapter(UserAdapter());
this._userBox = await Hive.openBox<User>('user');
}
storeUser(User user) {
this._userBox.put('user', user);
}
User getUser() {
return this._userBox.get('user');
}
}
Use in a ConsumerWidget:
class SomeWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(hiveProvider).data?.value;
...
}
}

flutter: Unhandled Exception: Bad state: Cannot add new events after calling close

I am trying to use the bloc pattern to manage data from an API and show them in my widget. I am able to fetch data from API and process it and show it, but I am using a bottom navigation bar and when I change tab and go to my previous tab, it returns this error:
Unhandled Exception: Bad state: Cannot add new events after calling
close.
I know it is because I am closing the stream and then trying to add to it, but I do not know how to fix it because not disposing the publishsubject will result in memory leak.
here is my Ui code:
class CategoryPage extends StatefulWidget {
#override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
#override
void initState() {
serviceBloc.getAllServices();
super.initState();
}
#override
void dispose() {
serviceBloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: serviceBloc.allServices,
builder: (context, AsyncSnapshot<ServiceModel> snapshot) {
if (snapshot.hasData) {
return _homeBody(context, snapshot);
}
if (snapshot.hasError) {
return Center(
child: Text('Failed to load data'),
);
}
return CircularProgressIndicator();
},
);
}
}
_homeBody(BuildContext context, AsyncSnapshot<ServiceModel> snapshot) {
return Stack(
Padding(
padding: EdgeInsets.only(top: screenAwareSize(400, context)),
child: _buildCategories(context, snapshot))
],
);
}
_buildCategories(BuildContext context, AsyncSnapshot<ServiceModel> snapshot) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisSpacing: 3.0),
itemCount: snapshot.data.result.length,
itemBuilder: (BuildContext context, int index) {
return InkWell(
child: CategoryWidget(
title: snapshot.data.result[index].name,
icon: Icons.phone_iphone,
),
onTap: () {},
);
},
),
);
}
here is my bloc code:
class ServiceBloc extends MainBloc {
final _repo = new Repo();
final PublishSubject<ServiceModel> _serviceController =
new PublishSubject<ServiceModel>();
Observable<ServiceModel> get allServices => _serviceController.stream;
getAllServices() async {
appIsLoading();
ServiceModel movieItem = await _repo.getAllServices();
_serviceController.sink.add(movieItem);
appIsNotLoading();
}
void dispose() {
_serviceController.close();
}
}
ServiceBloc serviceBloc = new ServiceBloc();
I did not include the repo and API code because it is not in the subject of this error.
Use StreamController.isClosed to check if the controller is closed or not, if not closed add data to it.
if (!_controller.isClosed)
_controller.sink.add(...); // safe to add data as _controller isn't closed yet
From Docs:
Whether the stream controller is closed for adding more events.
The controller becomes closed by calling the close method. New events cannot be added, by calling add or addError, to a closed controller.
If the controller is closed, the "done" event might not have been delivered yet, but it has been scheduled, and it is too late to add more events.
If the error is actually caused by the code you posted, I'd just add a check to ensure no new events are added after dispose() was called.
class ServiceBloc extends MainBloc {
final _repo = new Repo();
final PublishSubject<ServiceModel> _serviceController =
new PublishSubject<ServiceModel>();
Observable<ServiceModel> get allServices => _serviceController.stream;
getAllServices() async {
// do nothing if already disposed
if(_isDisposed) {
return;
}
appIsLoading();
ServiceModel movieItem = await _repo.getAllServices();
_serviceController.sink.add(movieItem);
appIsNotLoading();
}
bool _isDisposed = false;
void dispose() {
_serviceController.close();
_isDisposed = true;
}
}
ServiceBloc serviceBloc = new ServiceBloc();
I run into same error and noticed that if you check isClosed, the screen is not updated. In your code you have to remove the last line from Bloc file:
ServiceBloc serviceBloc = new ServiceBloc();
and put this line in CategoryPage just before the initState(). This way your widget is creating and disposing the bloc. Before, the widget only disposes the bloc but it is never re-created when the widget is re-created.
besides the provided solution I think you should also drain the stream allServices used in your ServiceBloc with:
#override
void dispose() {
...
allServices?.drain();
}
#cwhisperer is absolutely right. Initialize and dispose your block inside widget just like bellow.
final ServiceBloc serviceBloc = new ServiceBloc();
#override
void initState() {
serviceBloc.getAllServices();
super.initState();
}
#override
void dispose() {
serviceBloc.dispose();
super.dispose();
}
and delete ServiceBloc serviceBloc = new ServiceBloc(); from your class ServiceBloc
You should not worry about memory leak while using flutter_bloc as
When using bloc you do not need to close the bloc manually, if you have used a bloc provider to inject the bloc. Bloc Providers handle that for you out of the box as mentioned in the flutter_bloc docs.
BlocProvider is responsible for creating the bloc, it will automatically handle closing the bloc
You can test this in your application. Try printing on the close() override of bloc.
If the Screen at which the bloc was provided is removed from navigation stack then the close() method for that given bloc is called out of the box.
ServiceBloc serviceBloc = new ServiceBloc();
// remove this code
// don't init class in the same page that will cause of bad state.
I also faced this issue in production, and I realized that we should either dispose BehaviorSubject (or any other StreamController) when the Widget is disposed or Check to see if Stream is closed before adding new value.
Here is a nice extension to do all the job:
extension BehaviorSubjectExtensions <T> on BehaviorSubject<T> {
set safeValue(T newValue) => isClosed == false ? add(newValue) : () {};
}
You can use it like so:
class MyBloc {
final _data = BehaviorSubject<String>();
void fetchData() {
// get your data from wherever it is located
_data.safeValue = 'Safe to add data';
}
void dispose() {
_data.close();
}
}
How to dispose in Widget:
class CategoryPage extends StatefulWidget {
#override
_CategoryPageState createState() => _CategoryPageState();
}
class _CategoryPageState extends State<CategoryPage> {
late MyBloc bloc;
#override
void initState() {
bloc = MyBloc();
bloc.fetchData();
super.initState();
}
#override
void dispose() {
bloc.dispose();
super.dispose();
}
// Other part of your Widget
}
even better, if you aren't sure you won't reuse the stream after disposing:
call the drain() function on the stream before closing the stream.
dispose() async{
await _coinDataFetcher.drain();
_coinDataFetcher.close();
_isdisposed = true;
}
Check if the bloc/cubit is closed by isClosed variable. Wrap this if conditions to those states which are throwing exception.
Example code
class LandingCubit extends Cubit<LandingState> {
LandingCubit(this.repository) : super(LandingInitial());
final CoreRepository repository;
// Fetches image urls that needs to shown in landing page
void getLandingImages() async {
emit(LandingImagesLoading());
try {
List<File> landingImages = await repository.landingImages();
if (!isClosed) {
emit(LandingImagesSuccess(landingImages));
}
} catch (e) {
if (!isClosed) {
emit(LandingImagesFetchError(e.toString()));
}
}
}
}

Is it safe to have a Widget class that returns a widget conditionally?

Quite often I only want to draw a Widget based on a condition.
For example, I may be creating a component that displays a FadeIn.image but the image: may not be set in the CMS. In this case I want to ignore drawing the FadeIn.image and just return an empty container.
for context, I had done,
child: (someValue == null) ? new Container() : new LabelComponent(label: myStringLabel)
But this broke hot reload and I needed to replace with,
child: _createLabelComponent(myStringLabel),
Widget _createLabelComponent(String label)
{
if(label == null) {
return new Container();
} else {
return new LabelComponent(label: label)
}
}
Is the below safe to work and will not break hot reload? It seems to work at the moment but before I replace all my conditions with this Widget, I'd like some more feedback.
class ConditionalWidget extends StatefulWidget {
final bool condition;
final Widget conditionalWidget;
ConditionalWidget(this.condition, this.conditionalWidget, {Key key});
#override
State createState() => new ConditionalWidgetState();
}
class ConditionalWidgetState extends State<ConditionalWidget> {
ConditionalWidgetState();
#override
void initState()
{
super.initState();
}
#override
Widget build(BuildContext context)
{
if(widget.condition) {
return widget.conditionalWidget;
} else {
return new Container();
}
}
}
Nothing magic here, child is a property of a Container class:
it is safe to use whatever expression returns a widget for child property, obviously also conditional expressions
condition ? expr1 : expr2.
If hot reload in broken check for other causes.
For example your code that break hot reload:
child: (someValue == null) ? new Container() : new LabelComponent(label: myStringLabel)
uses someValue and myStringLabel, whereas in _createLabelComponent there is only a label variable.
class StatmentExample extends StatelessWidget {
Widget build(BuildContext context) {
return Text((() {
if(true){
return "tis true";}
return "anything but true";
})());
}
}
wrap your statements in a function
(() {
// your code here
}())

How to open Scaffold's Drawer on page load?

Logging into our Flutter app opens to dashboard that has a Scaffold with a Drawer full of menu items.
I'd like to perform some A/B testing with having the Drawer open on page load or at least animating the Drawer being opened immediately on load.
I'm aware of Scaffold.of(context).openDrawer() but I'm not sure where to place this code so that it will run immediately after the build() method. I'm also not aware of any fields on either Drawer or Scaffold which would load with the Drawer open.
Thanks for your time and help.
You need to wait after the first frame is loaded.
_onLayoutDone(_) {
//your logic here
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}
I wrote a post about this, you can take a look if you want : https://medium.com/#diegoveloper/flutter-widget-size-and-position-b0a9ffed9407
Override initState.
#override
void initState() {
super.initState();
// use this
Timer.run(() => Scaffold.of(context).openDrawer());
}
Store a state variable to hide and show drawer - isDrawerBeingShown.
Based on the state variable toggle the state of drawer. It is set to false by default so it will be displayed for the first time.
void _showDrawer(BuildContext context) async it must be marked as async so that it runs after build method.
Create showDrawerUtility method to show drawer on demand when ever required.
Edit:
Use GlobalKey
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
class MainScreen extends StatefulWidget {
MainScreen({Key key }) : super(key: key);
#override
State<MainScreen> createState() => new MainScreenState();
}
class MainScreenState extends State<MainScreen> {
bool isDrawerBeingShown;
#override
void initState() {
super.initState();
isDrawerBeingShown = false;
_showDrawer(context);
}
void _showDrawer(BuildContext context) async {
if(!isDrawerBeingShown) {
_scaffoldKey.currentState.openDrawer();
setState(() => isDrawerBeingShown = true);
}
}
#override
Widget build(BuildContext context) { // build method goes here}
}
follow my code
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey();
class openDrawerOnLoadPage extends StatefulWidget {
openDrawerOnLoadPage({Key? key}) : super(key: key);
#override
_openDrawerOnLoadPageState createState() => _openDrawerOnLoadPageState();
}
class _openDrawerOnLoadPageState extends State<openDrawerOnLoadPage> {
late bool isDrawerBeingShown;
#override
void initState() {
super.initState();
isDrawerBeingShown = false;
_showDrawer(context);
}
void _showDrawer(BuildContext context) async {
if (!isDrawerBeingShown) {
EasyDebounce.debounce('openDrawer', Duration(milliseconds: 100),
() async {
_scaffoldKey.currentState!.openDrawer();
setState(() => isDrawerBeingShown = true);
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
);
}
}

stream subscription throwing an error in dart in flutter app?

I am using the connectivity plugin in my flutter to check for the connection status, but occasionally hitting the error PlatForm Exception(No active stream to cancel, null) even though i have handled the null case. I have subscribed to the stream in initState and cancelled the subscription in dispose state
my code looks something like this.
StreamSubscription streamConnectionStatus;
------------
//remaining code
------------
#override
void initState() {
getConnectionStatus();
}
getConnectionStatus() async {
streamConnectionStatus = new Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
// Got a new connectivity status!
if (result == ConnectivityResult.mobile ||
result == ConnectivityResult.wifi) {
setState(() {
boolHasConnection = true;
});
} else {
setState(() {
boolHasConnection = false;
});
}
});
#override
void dispose() {
try {
streamConnectionStatus?.cancel();
} catch (exception, stackTrace) {
print(exception.toString());
updateError(exception.toString(), stackTrace);
} finally {
super.dispose();
}
}
this is actually driving me crazy but i am guessing i am missing something or do i have to change the code.
Many thanks,
Mahi
I encountered a similar issue. This is what helped me.
I had subscribed the stream exposed by connectivity plugin in different widgets in the same widget tree. I removed the subscription from child widgets and retained the subscription only in the parent and passed on the connection status to the children from parent.
By doing so my code got more cleaner and the stream subscription was maintained / disposed only at one place. Then I didn't encounter this issue any more.
I think your dispose function is defined inside getConnectionStatus. The IDE might not throw error as it still is a valid definition. Just remove it from inside and make sure it lies in the respective class. Your code just works like a charm.
Example:
class ConnectivityExample extends StatefulWidget {
#override
_ConnectivityExampleState createState() => new _ConnectivityExampleState();
}
class _ConnectivityExampleState extends State<ConnectivityExample> {
StreamSubscription streamConnectionStatus;
bool boolHasConnection;
#override
void initState() {
getConnectionStatus();
}
Future<Null> getConnectionStatus() async {
streamConnectionStatus = new Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
debugPrint(result.toString());
if (result == ConnectivityResult.mobile ||
result == ConnectivityResult.wifi) {
setState(() {
boolHasConnection = true;
});
} else {
setState(() {
boolHasConnection = false;
});
}
});
}
// dispose function inside class
#override
void dispose() {
try {
streamConnectionStatus?.cancel();
} catch (exception, stackTrace) {
print(exception.toString());
} finally {
super.dispose();
}
}
#override
Widget build(BuildContext context) {
return new Container(
color: Colors.white,
alignment: Alignment.center,
child: new Text(boolHasConnection.toString()),
);
}
}
Hope that helps!

Resources