Flutter qr_code_scanner on successfully scanned data update navigation? - flutter

This is a widget, that have function that triggers each time we scan qr code.
import 'package:qr_code_scanner/qr_code_scanner.dart';
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
flex: 4,
child: QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.red,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: 300,
),
),
),
In function I want to navigate to next screen.
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
});
}
Problem here is that that listen event trigger many times, is it possible to stop this function after first successfully scan data? I try with
controller.scannedDataStream.first;
But that return empty string and not triggers when real data are scanned.
I need to click 40 times to go back from Second Route to return to QR scanner widget.
Thanks!

for future reference i found pausing the camera works better :)
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
controller.scannedDataStream.listen((scanData) {
qrText = scanData;
SecondPageRoute();
});
}
SecondPageRoute() async {
controller?.pauseCamera();
var value = await Navigator.push(context,
MaterialPageRoute(builder: (context) {
return SecondPage(qrText);
})).then((value) => controller.resumeCamera());
}

If someone still has this problem, try this solution:) It works well for me!
onQRViewCreated: (QRViewController qrViewController) {
this.qrViewController = qrViewController;
qrViewController.scannedDataStream.listen((qrData) {
qrViewController.pauseCamera();
final String qrCode = qrData.code;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(
qrCode: '$qrCode',
))).then((value) => qrViewController.resumeCamera());
});
}

You can just add a sentinel
void _onQRViewCreated(QRViewController controller) {
this.controller = controller;
bool scanned = false;
controller.scannedDataStream.listen((scanData) {
if (!scanned) {
scanned = true;
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondRoute()),
);
}
});
}

I've faced the same issue, With lot of trial and error, found the solution qrViewController.dispose(). It's below.
onQRViewCreated: (QRViewController qrViewController) {
this.qrViewController = qrViewController;
qrViewController.scannedDataStream.listen((qrData) {
final String qrCode = qrData.code;
qrViewController.dispose();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondPage(
qrCode: '$qrCode',
))).then((value) => qrViewController.resumeCamera());
});
}

Related

How to reload data in initState() flutter while using pop

I have two screens, the first one fetches the data from the database and shows it on the screen. And the second screen creates a new user/course and sends it to the database. But when I use a pop to go back to the first screen, I want to update its data. How can I do this?
How can I update the screen data when returning from the second screen?
First Screen:
class CourseList extends StatefulWidget {
const CourseList({super.key});
#override
State<CourseList> createState() => _CourseListState();
}
class _CourseListState extends State<CourseList> {
Future<List<Course>?> listCourses = Future.value([]);
#override
initState() {
super.initState();
Future.delayed(Duration.zero, () {
setState(() {
var course = CoursesData();
listCourses = course.getCourses();
});
});
}
#override
Widget build(BuildContext context) {
var futureBuilder = FutureBuilder(
future: listCourses,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return createScreen(context, snapshot);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return const Center(child: CircularProgressIndicator());
});
return futureBuilder;
}
Widget createScreen(BuildContext context, AsyncSnapshot snapshot) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Navigator.of(context)
.pushNamed('/courseForm', arguments: null);
},
child: const Text("Adicionar Curso"))
],
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: ((context, index) {
return CourseTile(snapshot.data[index]);
}),
))
],
);
}
}
Second screen:
IconButton(
onPressed: () {
final isValid = _fomr.currentState!.validate();
Navigator.pop(context, true);
},
icon: const Icon(Icons.save))
EDITED:
ElevatedButton(
onPressed: () async {
var result = await Navigator.of(context)
.pushNamed('/courseForm', arguments: null);
if (result != null) {
setState(() {
var course = CoursesData();
listCourses = course.getCourses();
});
}
},
child: const Text("Adicionar Curso"))
You can navigate to second screen like this:
var result = await Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondScreen()));
if(result != null && result){
setState(() {
var course = CoursesData();
listCourses = course.getCourses();
});
}
then in your second screen when you pop like this
Navigator.pop(context, true);
as you are doing right now you pass back a bool variable to first screen which act like a flag and with that you can find when it is the time to reload the data. Also don forgot to await for the Navigator because of that you can receive the data from your second screen. Now when you come back from second screen, its going to update the ui.
The documentation covers this example in detail. So I'll paste in the relevant part:
Future<void> _navigateAndDisplaySelection(BuildContext context) async {
// Navigator.push returns a Future that completes after calling
// Navigator.pop on the Selection Screen.
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionScreen()),
);
The key is in this line:
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SelectionScreen()),
);
The result of navigator.push will be stored in result.
So in your case, you should do this after getting the result (as #eamirho3ein has answered first, so I'm explaining) :
setState(() {
var course = CoursesData();
listCourses = course.getCourses();
});
Take a look at the Flutter example for returning data

Flutter: Redirect to another page from widget

I am trying to implement a logout page. So when the user clicks on logout button in the navigation following code is called:
Class Logout extends StatelessWidget {
#override
Widget build(BuildContext context) {
final provider = Provider.of<SignInProvider>(context, listen: true);
Future.delayed(Duration(seconds: 5), () async {
provider.isLoggedIn = false;
provider.notifyListeners();
Navigator.pushReplacement(
context, new MaterialPageRoute(builder: (context) => LoginGate()));
});
return Center(child: CircularProgressIndicator());
}
}
I get the following error:
The following assertion was thrown building MainScreen(dirty, dependencies: [_InheritedProviderScope<SelectedIndex?>, _InheritedProviderScope<SignInProvider?>], state: _MainScreenState#6a8ce):
setState() or markNeedsBuild() called during build.
I tried adding the delay hoping that would fix the issue but didn't help. Would appreciate some help on how to handle this.
Logout Button is shown using NavigationRail
const NavigationRailDestination(
icon: Icon(Icons.logout),
label: Text('Logout'),
),
And the Logout widget is called using following:
child: Row(
children: [
NavigationRailExample(),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: screenSwitch[providerSelectedIndex.selectedIndex],
)
],
),
List<Widget> screenSwitch = [
HomeScreen(),
Screen1(),
Screen2(),
Screen3(),
Screen4(),
Screen5(),
Screen6(),
Logout(),
];
You are calling you async function in build method which is wrong. Try this:
class Logout extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: doLogOut(context),
builder: (context, snapshot) {
return Center(child: CircularProgressIndicator());
},
);
}
Future<void> doLogOut(BuildContext context) async {
final provider = Provider.of<SignInProvider>(context, listen: true);
await Future.delayed(Duration(seconds: 5), () async {
provider.isLoggedIn = false;
provider.notifyListeners();
Navigator.pushReplacement(
context, new MaterialPageRoute(builder: (context) => LoginGate()));
});
}
}

notifyListeners() does not return data to UI

I am new to flutter and need some help please
I have this screen with GoogleMaps (simplified for here):
class OrderDetailsScreen1 extends StatefulWidget {
const OrderDetailsScreen1({
Key? key,
}) : super(key: key);
#override
_OrderDetailsScreen1State createState() => _OrderDetailsScreen1State();
}
class _OrderDetailsScreen1State extends State<OrderDetailsScreen1> {
#override
Widget build(BuildContext context) {
final googleMapsNotifier =
Provider.of<GoogleMapsNotifier>(context, listen: true);
double latitude = -33.75;
double longitude = -70.67;
if (googleMapsNotifier.currentLocation != null) {
latitude = googleMapsNotifier.currentLocation!.latitude;
longitude = googleMapsNotifier.currentLocation!.longitude;
}
return Scaffold(
body: SafeArea(
child: SizedBox(
height: 300,
child: GoogleMap(
myLocationEnabled: true,
initialCameraPosition: CameraPosition(
target: LatLng(latitude, longitude),
zoom: 14,
),
),
),
),
);
}
}
I push this screen from the previous one like this
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => Provider<GoogleMapsNotifier>(
create: (_) => GoogleMapsNotifier(),
child: OrderDetailsScreen1(
),
)));
},
And I have a class GoogleMapsNotifier with ChangeNotifier like this
class GoogleMapsNotifier with ChangeNotifier {
final geolocatorService = GeolocatorService();
Position? currentLocation;
GoogleMapsNotifier() {
setCurrentLocation();
}
setCurrentLocation() async {
currentLocation = await geolocatorService.determinePosition();
print(currentLocation!.latitude);
notifyListeners();
}
}
Inside setCurrentLocation() of GoogleMapsNotifier it get currentLocation fine and prints it fine. But notifyListeners() does not pass data back to the UI (OrderDetailsScreen1). It does not react and I have no errors in the Console. Where is the mistake?
UPD: I tried to change the MaterialPageRoute to the below and it didn't help
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => Provider.value(
value: Provider.of<GoogleMapsNotifier>(context),
child: OrderDetailsScreen1(),
)));
},
You should pass the existing provider
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => Provider.value(
value: googleMapsNotifier,
child: OrderDetailsScreen1(
),
)));
},
You need to wrap your Ui widget with Consumer of specific provider. So it will be update state of your Ui through provider.

Statefulwidget is not refreshing ListView

I'm saving the data that is fetched from an API to the sqflite in flutter project, everything is working good, except that after clicking a raised button the data should be insert into the table and a new page should be open but there is no data unless I refresh that page so the data appear
As you can see, here is the code of the raised button:
RaisedButton(
child: Text('Get Cities'),
onPressed: () async {
setState(() {
GetAllData.data.Getdata();
});
await Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => StoreList()));
setState(() {});
},
)
Inside the setState I'm calling a function Getdata to get the data from the sqflite, after it getting it the app should open a new page
And below is the code of the page which should show the data in a ListView:
class StoreList extends StatefulWidget { #override
_StoreListState createState() => _StoreListState();}
class _StoreListState extends State<StoreList> {
#override void initState() {
super.initState();
setState(() {
DatabaseProvider_API.db.getRoutes();
});}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Stores List'),
),
body: FutureBuilder<List<Stores>>(
future: DatabaseProvider_API.db.getStores(),
builder: (context, snapshot){
if(snapshot.data == null){
return Center(
child: CircularProgressIndicator(),
);
}
else {
return ListView.separated(
separatorBuilder: (BuildContext context, int index){
return Divider();
},
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index){
String name = snapshot.data[index].sTORENAME;
String name_ar = snapshot.data[index].cITY;
return ListTile(
title: Text(name),
subtitle:Text (name_ar),
onTap: ()async{
setState(() {
});
await
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => Category() ));
},
);
},
);
}
},
),
floatingActionButton: new FloatingActionButton(
onPressed: () {
setState(() {});
},
child: new Icon(Icons.update),
),
);
}
}
Try to add the await keyword before evoke GetAllData.data.GetData()
RaisedButton(
child: Text('Get Cities'),
onPressed: () async {
// await for new data to be inserted
await GetAllData.data.Getdata();
await Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) => StoreList()));
setState(() {
dataFuture = GetAllData.data.Getdata();
});
},
)

close Simple Dialog in flutter when setState needs to called

I'm having a problem calling Navigator.of(context).pop() on my onPressed property in SimpleDialogOption widget. I need to set the state and dismiss the dialog. But calling setState is preventing my dialog to close. Without setState the dialog closes. Here is my dialog
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog(
builder: (BuildContext context) {
return SimpleDialog(
children: _children(suburbs),
backgroundColor: Colors.white,
title: Text('Pick your suburb'),
);
},
context: context);
});
and the method I use for the list of the Dialog:
List<Widget> _children(List<Suburb> suburbs) {
return suburbs
.map((suburb) => SimpleDialogOption(
onPressed: () {
print('#####################');
setState(() {
postcode = suburb.name;
});
Navigator.of(context).pop();
},
child: Text(suburb.name)))
.toList();
}
you can await until the return value comes from the navigator.pop,
and then call a setState
WidgetsBinding.instance.addPostFrameCallback((_) async {
postcode = await showDialog(
builder: (BuildContext context) {
return SimpleDialog(
children: _children(suburbs),
backgroundColor: Colors.white,
title: Text('Pick your suburb'),
);
},
context: context);
setState(() {
postcode;
});
});
List<Widget> _children(List<Suburb> suburbs) {
return suburbs
.map((suburb) => SimpleDialogOption(
onPressed: () {
print('#####################');
Navigator.of(context).pop(suburb.name);
},
child: Text(suburb.name)))
.toList();
}