How to show progress indicator while scanning barcode - flutter

#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.transparent,
// appBar: AppBar(
// title: Text('Future Demo Page'),
// ),
body: FutureBuilder(
builder: (ctx, snapshot) {
// Checking if future is resolved or not
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occured',
style: TextStyle(fontSize: 18),
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final data = snapshot.data as String;
getGoodsData(data);
// return getGoodsData(data);
}
}
// Displaying LoadingSpinner to indicate waiting state
return Center(
child: CircularProgressIndicator(),
);
},
// Future that needs to be resolved
// inorder to display something on the Canvas
future: startBarcodeScanStream(),
),
),
);
}
Future<void> startBarcodeScanStream() async {
FlutterBarcodeScanner.getBarcodeStreamReceiver(
'#ff6666', 'Cancel', true, ScanMode.BARCODE)
.listen((barcode) {
// if (!mounted) return;
this.barcodeScanner = barcode;
print(barcode);
// Don't show form if barcode sacnner is cancelled
if (barcode == "-1") {
Navigator.pop(context);
}
});
return barcodeScanner;
}
I am implementing barcode scanner in my flutter application. Once scanning is done I want to show progressIndicator at the center of scanning and I have to call the api.The above code is not showing any progress indicator.
Note: I want to show progressIndicator at the center of scanner after scanning and I have to call the api one response comes successfully I have to hide that progress indicator.

Easiest way is to add a new condition in your FutureBuilder that will detect that you are still waiting for the scan to complete and will display a loading until scan is done, then it will display normal widgets
Code:
body: FutureBuilder(
builder: (ctx, snapshot) {
// This is new code: state NOT "done"
if (snapshotGames.connectionState != ConnectionState.done) {
return Center(child: CircularProgressIndicator());}
}
// The rest of your code is the same
if (snapshot.connectionState == ConnectionState.done) {

You should add a bool variable to control the progress indicator and a Stack to put widget in front.
//Add variable
bool _loading=false;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
children:[
Positioned(
child: FutureBuilder(
builder: (ctx, snapshot) {
// Checking if future is resolved or not
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occured',
style: TextStyle(fontSize: 18),
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final data = snapshot.data as String;
getGoodsData(data);
// return getGoodsData(data);
}
}
// Displaying LoadingSpinner to indicate waiting state
return Center(
child: CircularProgressIndicator(),
);
},
// Future that needs to be resolved
// inorder to display something on the Canvas
future: startBarcodeScanStream(),
),
),
),
if(_loading==true) //show progress indicator if loading is true
Positioned(
child: Center(
child: CircularProgressIndicator(),
)
)
]
),
);
}
Future<void> startBarcodeScanStream() async {
setState(() {_loading=true;}); //start showing Circular Progress Indicator
FlutterBarcodeScanner.getBarcodeStreamReceiver(
'#ff6666', 'Cancel', true, ScanMode.BARCODE)
.listen((barcode) {
// if (!mounted) return;
this.barcodeScanner = barcode;
print(barcode);
// Don't show form if barcode sacnner is cancelled
if (barcode == "-1") {
Navigator.pop(context);
}
});
return barcodeScanner;
}

Related

How to update stream.length of FirebaseFirestore data after record addition or deletion?

The code below displays list of records from FirebaseFirestore using AsyncSnapshot with StreamBuilder. It works great, however I want to display the total number of records in the AppBar title and tht works when the app is launched, but doesn't update after any addition or deletion.
Question: How can I update the number of records (and display in Appbar title) after the list has an addition or deletion?
Note that I'm displaying the total number of records in the AppBar title using title: Text('# Waiting: $numberWaiting'),, but I can't figure out how to refresh this after the list changes. Any suggestions are greatly appreciated.
class HomePageState extends State<HomePage> {
Query waitingList = FirebaseFirestore.instance
.collection('waiting')
.orderBy('Time_In');
int numberWaiting = 0; // Starts at 0; updated in StreamBuilder
Future<void> delete(String docID) async {
await FirebaseFirestore.instance.collection('waiting').doc(docID).delete();
// TODO: How to update numberWaiting in AppBar title?
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("# Waiting: ${numberWaiting.toString()}"),
),
body: SizedBox(
width: double.infinity,
child: Center(
child: StreamBuilder(
stream: waitingList.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Column(
...
);
}
else if (snapshot.hasData) {
return ListView.builder (
itemCount: snapshot.data?.docs.length,
itemBuilder: (BuildContext context, index) {
numberWaiting = index + 1;
String name = snapshot.data?.docs[index]['Name'];
return Card(
child: SizedBox(
child:ListTile(
title:
Row(
children: <Widget>[
Text(name),
],
),
onTap: () {
// Create or Update Record
// TODO: Update numberWaiting for title
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context){
return CrudPage(
docId: snapshot.data?.docs[index].id.toString() ?? "",
docSnap: snapshot.data?.docs[index]);
}));
},
onLongPress: () {
// Delete Record
// TODO: Update numberWaiting for title
delete(snapshot.data?.docs[index].id.toString() ?? "");
},
),
),
);
},
);
}
else {
return const Text('No Data');
}
}, // Item Builder
),
),
),
);
}
}
Unfortunately this code only updates the # Waiting: X title once and doesn't refresh when an item is deleted or added.
Thank you for your help!
Simply update value and rebuild on "else if (snapshot.hasData)"
class HomePageState extends State {
Query waitingList = FirebaseFirestore.instance
.collection('waiting')
.orderBy('Time_In');
Future<int> countStream(Stream<QuerySnapshot<Object?>> stream) async =>
stream.length;
#override
Widget build(BuildContext context) {
var numberWaiting = "";
return Scaffold(
appBar: AppBar(
title: Text("# Waiting: $numberWaiting"),
),
body: SizedBox(
width: double.infinity,
child: Center(
child: StreamBuilder(
stream: waitingList.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Column(
...
);
}
else if (snapshot.hasData) {
setState((){
numberWaiting = snapshot.data?.docs.length.toString();
})
return ListView.builder (
itemCount: snapshot.data?.docs.length,
itemBuilder: (BuildContext context, index) {
String name = snapshot.data?.docs[index]['Name'];
return Card(
child: SizedBox(
child:ListTile(
title:
Row(
children: <Widget>[
Text(name),
],
),
),
),
);
},
);
}
else {
return const Text('No Data');
}
}, // Item Builder
),
),
),
);
}
}

FutureBuilder gives an unexpected result and slowing UI down

It is a first statefull widget
bool _isPressed = false;
...
ElevatedButton(
child: const Text('Run long calculations'),
onPressed: () {
setState(() {
_isPressed = !_isPressed;
});
},
),
_isPressed ? const Result() : Container(),
...
and Result widget with its builds function returns
FutureBuilder<String>(
future: _process(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: LinearProgressIndicator(),
);
} else {
if (snapshot.error != null) {
return const Center(
child: Text('An error occurred'),
);
} else {
return Text('${snapshot.data}');
}
}
},
);
Future<String> _process() async {
await argon2.hashPasswordString('dummy text', salt: Salt.newSalt()); // long calculations
return 'dummy result';
}
Why the FutureBuilder does not render LinearProgressIndicator before it render final text? Actualy, the LinearProgressIndicator is rendered for a very small amount of time before final text rendered, but there is something wrong with it, because the circular indicator should spin much longer.
_process() seems to slow down the application and that's why the progress indicator does not spin. But how can it be if the result of the computation is Future and the code awaits for it...
I think its better to change your conditions like below .
based on flutter Doc
if (snapshot.hasData) {
// data
return Text('${snapshot.data}');
} else if (snapshot.hasError) {
// error
} else {
// CircularProgressIndicator
return SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
);
}
If this answer does not help you and you think have a UI freeze because of heavy task in _process() method you should do the process task in separate Isolate.
Your code is fine, if you replace the _getHash body with just a Future.delayed() the progress indicator shows fine. Hence the problem is in hashPasswordString. If you look at the implementation of this function you'll notice that in fact it is synchronous.
So the quick fix would be - create a static function like that:
static String _calculateHash(String input) {
final result = argon2.hashPasswordStringSync(input,
salt: Salt.newSalt(), iterations: 256, parallelism: 8);
return result.hexString;
}
and use it with the compute function:
Future<String> _hash() {
return compute(_calculateHash, 'input text');
// this is not needed anymore
// DArgon2Result result = await argon2.hashPasswordString('input text',
// salt: Salt.newSalt(), iterations: 256, parallelism: 8);
// return result.hexString;
}
static String _calculateHash(String input) {
final result = argon2.hashPasswordStringSync(input,
salt: Salt.newSalt(), iterations: 256, parallelism: 8);
return result.hexString;
}
The long and proper fix - create a PR for the dargon2_flutter package.
The problem is with this line:
if (snapshot.connectionState == ConnectionState.waiting)
You see, ConnectionState.waiting is used when there is no connection yet, for example when a stream has no value.
Here is what each connection state is:
Active
after an asyncronous computation started, but before it ends.
None
When there is no asyncronous computation at all (for example, the future is None on a future builder)
Done
After the asyncronous computation has ended
Waiting
Before the asynchronous computation begins
So when you check if the connection state is waiting, the value is true for a split second and then the connection state switches to active, here is what your if statement should look like:
if (snapshot.connectionState == ConnectionState.active)
MohandeRr has suggested the impmentation flutter docs has used, but i usually do it like this
if (snapshot.connectionState != ConnectionState.done) {
return const Center(
child: LinearProgressIndicator(),
);
}
if (snapshot.hasError) {
return const Center(
child: Text('An error occurred'),
);
}
return Text('${snapshot.data}');
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool _isPressed = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Demo"),
actions: [
TextButton(
child: const Text(
'Press',
style: TextStyle(
color: Colors.white,
),
),
onPressed: () {
setState(() {
_isPressed = !_isPressed;
});
},
)
],
),
body: _isPressed
? FutureBuilder<String>(
future: process(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(
child: Text(snapshot.data ?? ""),
);
} else if (snapshot.hasError) {
return const Center(
child: Text('An error occurred'),
);
} else {
return const Center(
child: LinearProgressIndicator(),
);
}
},
)
: const Center(
child: Text("Hidden"),
),
);
}
Future<String> process() async {
await Future.delayed(const Duration(seconds: 3));
return "Hello World";
}
}
There are 2 problems at play here:
You are creating a new Future (_process()) for every build loop. You need to put that in your state and reuse/clear it appropriately.
You are using the wrong ConnectionState check - snapshot.connectionState != ConnectionState.done is probably what you need

StreamBuilder is not showing data from firestore

I am using streambuilder to display snapshot data but it is not displaying. The screen is just blank but When I use the future builder with get() methode it display the data but I want realtime changes. I am new to flutter please help me with this. here is code.
class TalentScreen2 extends StatelessWidget {
final Query _fetchFavUser = FirebaseRepo.instance.fetchFavUsers();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
Text('Talent Screen 2(Favourites)'),
Expanded(child: _retrieveData(context))
],
),
),
);
}
Widget _retrieveData(BuildContext context) => StreamBuilder<QuerySnapshot>(
stream: _fetchFavUser.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) return const Text('Something went wrong');
if (!snapshot.hasData) return const Text('Alas! No data found');
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: CircularProgressIndicator(
strokeWidth: 2.0,
));
if (snapshot.connectionState == ConnectionState.done)
return theUserInfo(snapshot.data.docs);
return Container();
});
Widget theUserInfo(List<QueryDocumentSnapshot> data) {
return ListView.builder(
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
var uid = data[index]['uid'];
TalentHireFavModel userData = TalentHireFavModel.fromMap(
data[index].data(),
);
return Card(
child: Column(
children: <Widget>[
Text(data[index]['orderBy']),
// Text(userData.name ?? ''),
Text(userData.categories),
Text(userData.skills),
// Text(userData.country ?? ''),
Text(userData.phoneNo),
Text(userData.hourlyRate),
Text(userData.professionalOverview),
Text(userData.skills),
Text(userData.expert),
// Text(userData.createdAt ?? ''),
_iconButton(userData.uid, context),
],
),
);
});
}
Future<DocumentSnapshot> fetch(data) async =>
await FirebaseRepo.instance.fetchWorkerUserData(data);
Widget _iconButton(uid, context) {
return IconButton(
icon: Icon(Icons.favorite),
onPressed: () {
BlocProvider.of<TalentFavCubit>(context).removeTalentFav(uid);
});
}
}
and here is the firestore query methode where I am just applying simple query to fetch all documents and display them. I want real-time changes
Query fetchFavUsers() {
var data = _firestore
.collection('workerField')
.doc(getCurrentUser().uid)
.collection('favourites')
// .where('uid', isNotEqualTo: getCurrentUser().uid)
.orderBy('orderBy', descending: true);
return data;
}
The solution is to just return the function. Get that method out of if statement and place it in just return statement.

Close loading dialog in FutureBuilder

I am using Futurebuilder in flutter and having issue while closing the showDialog
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size( 50.0),
body: FutureBuilder(
future: widget.getAutoCompleteData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
Navigator.of(context).pop();
} else if (snapshot.hasError) {
} else {
LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(
child: Container(child: Text("sds"),),
);
}));
}
Getting below error when screen loads
package:flutter/src/widgets/navigator.dart': Failed assertion: line 5013 pos 12: '!_debugLocked': is not true
Change this
FutureBuilder(
future: widget.getAutoCompleteData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
Navigator.of(context).pop();
} else if (snapshot.hasError) { //here this is empty
} else {//remove else statement
LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(
child: Container(child: Text("sds"),),
);
})
To This
FutureBuilder<List<Post>>(
future: _dataFetcher.getPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(child: Text('${snapshot.data![0].title}'));
},
)
It may be caused by the re-entrant of the Navigator (you can check the answer here: Error thrown on navigator pop until : “!_debugLocked': is not true.”
)
Or, maybe you don't want to use FutureBuilder. The FutureBuilder is meant to stay in the same widget/page and show different screens when future data is not ready. If you want to push a loading dialog and close it when data is ready, you can just simply use a Future function
Future pressTheButton(context) async {
LoadingDialog.showLoadingDialog(context, _scaffoldKey); // showDialog here
final data = await getAutoCompleteData(); // await the data
Navigator.of(context).pop(); // pop the loading dialog
// return your data or error
// or rebuild the current widget with the data
}

Open new screen when backend response is success

I have a SplashScreen that retrieve information from my Api. When response is success i would like open my second screen but I have a error because after my method to open new screen I need to add some return widget.
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: StreamBuilder(
stream: bloc.appInformation,
builder: (context, AsyncSnapshot<AppInformationModel> snapshot){
return onResponse(snapshot);
},
),
);
}
My response method look like this.
Widget onResponse(AsyncSnapshot<AppInformationModel> snapshot) {
if (snapshot.hasData) {
openMain();
return Center(
child: Text(
snapshot.data.version,
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
);
} else if (snapshot.hasError) {
return Text('Error');
}
return Center(child: CircularProgressIndicator());
}
And openMain() is just
void openMain() {
Navigator.pushNamed(context, 'home');
}
My second screen work perfect.
The error is
setstate or markneedsbuild called during build onTap
I understand is because my return widget is after opanNavigation() but i must return a view.
Thanks