Related
I have built a page which reads the json from recipeURL and I wish for it to display the product_name value in the json file. However for some reason my future fetchData () class isn't being read as none of the text in my if else statement is being displayed. Am I missing a simple oversight here?
EDIT: The main dart file is my main screen. This is where my navbar is created. Users are redirected to other pages when they click on the corresponding icon. Im having trouble passing BarcodePage(title:title); as parameters in my main file,26th line, can be found under Class MyAppState extends State<MyApp> {
My main dart file:
import 'package:flutter/material.dart';
import 'pages/BarcodePage.dart';
import 'pages/FoodPage.dart';
import 'pages/RecipePage.dart';
import 'pages/ShoppingPage.dart';
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),);
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState(){
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
int _selectedPage =0;
final _pageOptions= [
FoodPage(),
RecipePage(),
BarcodePage(title: ,),
ShoppingPage(),
];
#override
Widget build(BuildContext context) {
return MaterialApp(
//title: 'Best B4',
theme: ThemeData(
primarySwatch: Colors.teal,),
debugShowCheckedModeBanner: false,
home: Scaffold (
appBar: AppBar(
title:Text(
'BestB4',
style: TextStyle(
fontFamily: 'PacificoRegular',
fontSize: 30,
),
),
backgroundColor: Colors.teal,
elevation: 20,
actions: [
IconButton(
icon: Icon(Icons.qr_code_2_rounded),
tooltip: 'Add item',
onPressed:(){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BarcodePage()));
},
)
],
//ONPRESSED MENU OPEN
),
body:_pageOptions[_selectedPage],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: Colors.teal,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white70,
iconSize: 40,
selectedFontSize: 15,
unselectedFontSize: 15,
currentIndex:_selectedPage,
onTap: (int index) {
setState(() {
_selectedPage = index;
});
},
items: [
BottomNavigationBarItem(
icon:Icon(Icons.restaurant_rounded),
label: 'Food',
), //, title:Text('Food')
BottomNavigationBarItem(
icon:Icon(Icons.menu_book_rounded),
label:'Recipes',
),//, title:Text('Recipes')
BottomNavigationBarItem(
icon:Icon(Icons.add_outlined),
label:'Add',
),//, title:Text('Add')
BottomNavigationBarItem(
icon:Icon(Icons.shopping_cart_rounded),
label:'Shopping',
),//,title:Text('Shopping')
],
),
),
);
}
}
My BarcodePage dart file:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as p;
import 'package:flutter/services.dart';
import 'package:barcode_scan2/barcode_scan2.dart';
import 'package:http/http.dart';
class BarcodePage extends StatefulWidget{
const BarcodePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_BarcodePageState createState() => _BarcodePageState();
}
var futuredata = {};
class _BarcodePageState extends State<BarcodePage> {
int counter=0;
String result= "";
Future _scanBarcode() async{
try{
ScanResult scanResult = await BarcodeScanner.scan();
String barcodeResult = scanResult.rawContent;
setState(() {
result = barcodeResult;
});
} on PlatformException catch (ex) {
if (ex.code == BarcodeScanner.cameraAccessDenied) {
setState((){
result = "CAMERA PERMISSION WAS DENIED. \n EDIT THIS IN YOUR SETTINGS";
});
}else {
setState(() {
result = "404 ERROR UNKNOWN $ex";
});
}
} on FormatException {
setState(() {
result = "SCAN AN ITEM";
});
} catch (ex){
setState(() {
result = "404 ERROR UNKNOWN $ex";
});
}
}
#override
void initState() {}
fetchmydata() async {
var request = p.Request(
'GET',
Uri.parse(
'https://world.openfoodfacts.org/api/v0/product/5060391623139.json'));
StreamedResponse response = await request.send();
if (response.statusCode == 200) {
// print(await response.stream.bytesToString());
var data = await response.stream.bytesToString();
futuredata = json.decode(data);
} else {
print(response.reasonPhrase);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.teal,
title:Text('Add an item',
style: TextStyle(
fontFamily: 'Fredoka',
fontSize: 25,
),
),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: Container(
child: CircularProgressIndicator(),
height: 50,
width: 50,
),
);
else if (snapshot.connectionState == ConnectionState.done)
return ListTile(
title: Text(futuredata["product"]["product_name"].toString()),
subtitle: Text("France:" +
futuredata["product"]["product_name_en"].toString()),
);
else {
return Container(
child: CircularProgressIndicator(),
height: 50,
width: 50,
);
}
},
future: fetchmydata(),
)
],
),
floatingActionButton: FloatingActionButton.extended(
backgroundColor: Color.fromRGBO(51, 171, 160, 100),
icon: Icon(Icons.camera_alt),
label: Text("Scan"),
onPressed: _scanBarcode,
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
);
}
}
The Json file looks like this:
JSON LINK: https://world.openfoodfacts.org/api/v0/product/5060391623139.json
You can read your json like this
var futuredata = {};
var productname= futuredata["product"]["product_name"]
Your fetch method like this
fetchmydata() async {
var request = p.Request(
'GET',
Uri.parse(
'https://world.openfoodfacts.org/api/v0/product/5060391623139.json'));
StreamedResponse response = await request.send();
if (response.statusCode == 200) {
// print(await response.stream.bytesToString());
var data = await response.stream.bytesToString();
futuredata = json.decode(data);
} else {
print(response.reasonPhrase);
}
}
With model Class
SampleModel? Mymodel = null;
fetchmydata() async {
var request = p.Request(
'GET',
Uri.parse(
'https://world.openfoodfacts.org/api/v0/product/5060391623139.json'));
StreamedResponse response = await request.send();
if (response.statusCode == 200) {
// print(await response.stream.bytesToString());
var data = await response.stream.bytesToString();
futuredata = json.decode(data);
Mymodel = SampleModel.fromJson(futuredata);
} else {
print(response.reasonPhrase);
}
}
in the method we first read data from json as string or text
then we decode the string type or text from server to map type
SampleCode Dartpad live code check
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:http/http.dart' as p;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(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(
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
var futuredata = {};
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
#override
void initState() {}
fetchmydata() async {
var request = p.Request(
'GET',
Uri.parse(
'https://world.openfoodfacts.org/api/v0/product/5060391623139.json'));
StreamedResponse response = await request.send();
if (response.statusCode == 200) {
// print(await response.stream.bytesToString());
var data = await response.stream.bytesToString();
futuredata = json.decode(data);
} else {
print(response.reasonPhrase);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
FutureBuilder(
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: Container(
child: CircularProgressIndicator(),
height: 50,
width: 50,
),
);
else if (snapshot.connectionState == ConnectionState.done)
return ListTile(
title: Text(futuredata["product"]["product_name"].toString()),
subtitle: Text("France:" +
futuredata["product"]["product_name_en"].toString()),
);
else {
return Container(
child: CircularProgressIndicator(),
height: 50,
width: 50,
);
}
},
future: fetchmydata(),
)
],
),
);
}
}
Sample ModelClass
///
/// Code generated by jsonToDartModel https://ashamp.github.io/jsonToDartModel/
///
class SampleModelProduct {
/*
{
"_id": "5060391623139",
"_keywords": [
"peanut"
],
"product_name": "Peanut butter",
"product_name_en": "Peanut butter",
"product_name_fr": "Peanut butter"
}
*/
String? Id;
List<String?>? Keywords;
String? productName;
String? productNameEn;
String? productNameFr;
SampleModelProduct({
this.Id,
this.Keywords,
this.productName,
this.productNameEn,
this.productNameFr,
});
SampleModelProduct.fromJson(Map<String, dynamic> json) {
Id = json['_id']?.toString();
if (json['_keywords'] != null) {
final v = json['_keywords'];
final arr0 = <String>[];
v.forEach((v) {
arr0.add(v.toString());
});
Keywords = arr0;
}
productName = json['product_name']?.toString();
productNameEn = json['product_name_en']?.toString();
productNameFr = json['product_name_fr']?.toString();
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['_id'] = Id;
if (Keywords != null) {
final v = Keywords;
final arr0 = [];
v!.forEach((v) {
arr0.add(v);
});
data['_keywords'] = arr0;
}
data['product_name'] = productName;
data['product_name_en'] = productNameEn;
data['product_name_fr'] = productNameFr;
return data;
}
}
class SampleModel {
/*
{
"code": "5060391623139",
"product": {
"_id": "5060391623139",
"_keywords": [
"peanut"
],
"product_name": "Peanut butter",
"product_name_en": "Peanut butter",
"product_name_fr": "Peanut butter"
},
"status": 1,
"status_verbose": "product found"
}
*/
String? code;
SampleModelProduct? product;
int? status;
String? statusVerbose;
SampleModel({
this.code,
this.product,
this.status,
this.statusVerbose,
});
SampleModel.fromJson(Map<String, dynamic> json) {
code = json['code']?.toString();
product = (json['product'] != null)
? SampleModelProduct.fromJson(json['product'])
: null;
status = json['status']?.toInt();
statusVerbose = json['status_verbose']?.toString();
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['code'] = code;
if (product != null) {
data['product'] = product!.toJson();
}
data['status'] = status;
data['status_verbose'] = statusVerbose;
return data;
}
}
try creating a .g.dart file using flutter packages pub run build_runner build. flutter automatically will create your binding class factory. once you have the binding classes created flutter will automatically code your binding class, including nested classes. I personally think automation is the way to handle all interactions with json from the server. The reason you want to use the future .g.dart code generator is to reduce the possibility of error and incorrect type casting.
file.dart
factory ProductView.fromJson(Map<String, dynamic> json) =>
_$ProductFromJson(json);
Map<String, dynamic> toJson() => _$ProductViewToJson(this);
file.g.dart
ProductView _$ProductViewFromJson(Map<String, dynamic> json) {
return
ProductView(
json['field1'] as int,
json['field2'] as String,
json['field3'] == null
? null
: DateTime.parse(json['field3'] as String),
);
}
Map<String, dynamic> _$ProjectViewToJson(ProductView instance) =>
<String, dynamic>{
'field1': instance.field1,
'field2': instance.field2,
'field3': instance.field3?.toIso8601String(),
};
decoding the json
var client = http.Client();
Map<String, String> headers = new HashMap();
headers['Accept'] = 'application/json';
headers['Content-Type'] = 'application/json';
headers['Authorization'] = 'Bearer $token';
var response = await client.get(url, headers: headers).timeout(_TIMEOUT);
var parsed = json.decode(response.body);
var view = ProductView.fromJson(parsed);
The code flow doesn't even enter the onIceCandidate function while answering the SDP for webRTC connection. The webRTC is used for Voice calling for VOIP in android and I have also setted up TURN server with viagene website.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'WebRTC lets learn together'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CollectionReference firebaseInstance =
FirebaseFirestore.instance.collection("dmeet");
RTCPeerConnection _peerConnection;
MediaStream _localStream;
RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
var docId = TextEditingController();
var l;
var document;
_createOfferSdp() async {
RTCSessionDescription description =
await _peerConnection.createOffer({'offerToReceiveAudio': 1});
Map<String, dynamic> session = {"sdp": description.sdp};
document = firebaseInstance.doc();
document.collection("sdp").doc("offersdp").set(session);
await _peerConnection.setLocalDescription(description);
document.collection("icecandidate").snapshots().listen((result) async {
dynamic candidate = new RTCIceCandidate(
result['candidate'], result['sdpMid'], result['sdpMlineIndex']);
await _peerConnection.addCandidate(candidate);
});
print(session);
_peerConnection.onIceCandidate = (event) {
if (event.candidate != null) {
Map<String, dynamic> icecandidate = {
"candidate": event.candidate,
"sdpMid": event.sdpMid,
"sdpMlineIndex": event.sdpMlineIndex
};
document.collection("candidate").doc().set(icecandidate);
}
};
}
bool remotesaved = false;
_createAnswerSdp() async {
_peerConnection.onIceCandidate = (event) {
print("Candiate ${event.candidate}");
if (event.candidate != null) {
// Map<String, dynamic> icecandidate = {
// "candidate": event.candidate,
// "sdpMid": event.sdpMid,
// "sdpMlineIndex": event.sdpMlineIndex
// };
// document.collection("candidate").doc().set(icecandidate);
print("Candidate: ${event.candidate}");
}
};
firebaseInstance
.doc(docId.text)
.collection("sdp")
.doc("offersdp")
.get()
.then((value) async {
var remoteSession = value.data()["sdp"];
RTCSessionDescription description1 =
RTCSessionDescription(remoteSession, "offer");
await _peerConnection
.setRemoteDescription(description1)
.then((value) async {
RTCSessionDescription description2 =
await _peerConnection.createAnswer({'offerToReceiveAudio': 1});
Map<String, dynamic> session = {"sdp": description2.sdp};
firebaseInstance
.doc(docId.text)
.collection("sdp")
.doc("answersdp")
.set(session);
final iceCandidate = await firebaseInstance
.doc(docId.text)
.collection("candidate")
.get();
iceCandidate.docs.forEach((element) async {
print("Candidate ${element.data()["candidate"]}");
dynamic candidate = RTCIceCandidate(element.data()['candidate'],
element.data()['sdpMid'], element.data()['sdpMlineIndex']);
await _peerConnection.addCandidate(candidate);
});
});
});
}
showAlertDialog(BuildContext context) {
// set up the buttons
Widget cancelButton = FlatButton(
child: Text("Cancel"),
onPressed: () {},
);
Widget continueButton = FlatButton(
child: Text("Continue"),
onPressed: _createAnswerSdp,
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("AlertDialog"),
content: TextField(
controller: docId,
),
actions: [
cancelButton,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
initRenderer() async {
await _remoteRenderer.initialize();
}
#override
void initState() {
_createPeerConnection().then((pc) {
_peerConnection = pc;
});
initRenderer();
// _localStream.initialize();
super.initState();
}
#override
void dispose() {
_remoteRenderer.dispose();
super.dispose();
}
_getUserMedia() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': false,
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
// _localStream = stream;
// _peerConnection.addStream(stream);
return stream;
}
_createPeerConnection() async {
Map<String, dynamic> configuration = {
"iceServers": [
{"url": "stun:stun.l.google.com:19302"},
{
"url": "turn:numb.viagenie.ca",
"username": "******#gmail.com",
"credential": "*****",
}
]
};
final Map<String, dynamic> offerSdpConstraints = {
"mandatory": {
"OfferToReceiveAudio": true,
"OfferToReceiveVideo": false,
},
"optional": [],
};
_localStream = await _getUserMedia();
RTCPeerConnection pc =
await createPeerConnection(configuration, offerSdpConstraints);
pc.addStream(_localStream);
pc.onIceCandidate = (e) {
if (e.candidate != null) {
l = json.encode({
'candidate': e.candidate.toString(),
'sdpMid': e.sdpMid.toString(),
'sdpMlineIndex': e.sdpMlineIndex,
});
print("Her $l");
}
};
pc.onAddStream = (stream) {
print('addStream: ' + stream.id);
_remoteRenderer.srcObject = stream;
};
return pc;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: Row(
children: [
Flexible(child: RTCVideoView(_remoteRenderer)),
ElevatedButton(
child: Text("Create"),
onPressed: _createOfferSdp,
),
ElevatedButton(
onPressed: () {
showAlertDialog(context);
},
child: Text("Join"),
)
],
),
),
),
);
}
}
The Line that does not even entered is the function _createAnwerSdp() and next line to it!
The createAnswerSdp function is used for answering the call while getting the ice candidate.
What may be cause for the issue?
So, I can clearly see that there you haven't set any local description for the remote user who is going to answer this call.
_peerConnection.setLocalDescription(description2);
Hope this might help!
I want to add dynamycally a ListTile when I receive data from a websocket.
The ListView seems not ready when I add the new data to my global list.
I'm getting this error
[ERROR:flutter/shell/common/shell.cc(242)] Dart Unhandled Exception: setState() called in constructor: _MyHomePageState#4c6c6(lifecycle state: created, no widget, not mounted)
The error happened when this code is processed
void addItem(Item item) {
setState(() {
items.add(item);
});
}
I tried to apply these solutions but none works for me :
Flutter Dart setState not working: Unhandled Exception: setState() called in constructor: ...(lifecycle state: created, no widget, not mounted)
Flutter Unhandled Exception: setState() called in constructor:
Here is my test app :
dependencies
dependencies:
flutter:
sdk: flutter
web_socket_channel: ^2.0.0
intl: ^0.17.0
main.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:test_add_from_websocket/Item.dart';
import 'package:test_add_from_websocket/websocket.dart';
void main() {
runApp(MyApp());
}
Future _init() async {
final WebSocket _socket = WebSocket();
_socket.connect('wss://echo.websocket.org', connectionListener, messageListener);
}
void connectionListener(WebSocket socket, bool connected) {
print('Status : ' + (connected ? 'Connected' : 'Failed to connect'));
}
void messageListener(WebSocket socket, String message) {
print('websocket received: $message');
Map itemMap = jsonDecode(message);
var _item = Item.fromJson(itemMap);
MyHomePage().addItem(_item);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: FutureBuilder(
future: _init(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return MyHomePage();
} else {
return Text('Loading');
}
},
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
void addItem(Item item) {
_MyHomePageState().addItem(item);
}
}
class _MyHomePageState extends State<MyHomePage> {
void addItem(Item item) {
setState(() {
items.add(item);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add from websocket event'),
),
body: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.access_time),
title: Text(items[index].time),
);
}),
floatingActionButton: FloatingActionButton(
onPressed: () {
DateTime now = DateTime.now();
Item _item = Item(
time: DateFormat.Hm().format(now),
);
print(_item.time);
final WebSocket _socket = WebSocket();
_socket.send(jsonEncode(_item));
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
My WebSocket class
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:web_socket_channel/status.dart' as status;
class WebSocket {
static final WebSocket _instance = WebSocket._internal();
factory WebSocket() => _instance;
WebSocket._internal();
String _url;
bool _connected;
WebSocketChannel _channel;
connect(String url, Function connectionListener, Function messageListener) {
try {
_url = url;
_channel = IOWebSocketChannel.connect(_url);
_connected = true;
connectionListener(_instance, _connected);
print('websocket connected to $_url');
_channel.stream.listen(
(message) {
print('websocket receive $message');
messageListener(_instance, message);
},
onDone: () {
_connected = false;
connectionListener(_instance, _connected);
print('websocket closed');
},
onError: (error) {
print('websocket error $error');
},
);
} catch (e) {
print('websocket exception $e');
_connected = false;
connectionListener(_connected);
}
}
close() {
if (_connected) {
_channel.sink.close(status.normalClosure);
}
}
bool send(String message) {
try {
if (_channel == null) throw ('_channel undefined');
_channel.sink.add(message);
return true;
} catch (e) {
return false;
}
}
}
And my Item class
class Item {
final String time;
Item({this.time});
Item.fromJson(Map<String, dynamic> json) : time = json['time'];
Map<String, dynamic> toJson() => {'time': time};
}
List<Item> items = [
Item(time: '10:22'),
Item(time: '11:22'),
];
Thanks
the problem is that you are calling addItem which call setState however there is no state yet when you called it
try to move your global functions to the state class and call them from initState
I have added some routes to my flutter app so that when a specific link is pressed a specific page will be opened
This is the source code of the main page
import 'dart:convert';
import 'package:admob_flutter/admob_flutter.dart';
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'package:catcher/catcher.dart';
import 'package:dynamic_theme/dynamic_theme.dart';
import 'package:firebase_admob/firebase_admob.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:videos/c.dart';
import 'package:flutter/foundation.dart';
import 'package:videos/post.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;
import 'database/database.dart';
import 'database/database/mobile.dart' as mobile;
import 'src/app_route.dart';
void main() async{
WidgetsFlutterBinding.ensureInitialized();
FirebaseAdMob.instance.initialize(appId: APP_ID);
Admob.initialize(APP_ID);
await AndroidAlarmManager.initialize();
CatcherOptions debugOptions =CatcherOptions(DialogReportMode(), [kIsWeb?ConsoleHandler(): EmailManualHandler(["ahmad.rajab#windowslive.com"])]);
CatcherOptions releaseOptions = CatcherOptions(DialogReportMode(), [
kIsWeb?ToastHandler():EmailManualHandler(["ahmad.rajab#windowslive.com"])
]);
Catcher(MyApp(), debugConfig: debugOptions, releaseConfig: releaseOptions);
}
class MyApp extends StatefulWidget{
#override
State<StatefulWidget> createState()=>MyAppState();
}
class MyAppState extends State<MyApp> {
static Future configureDatabase()async{
/*database = await openDatabase(DATABASE_NAME, version: DATABASE_VERSION,
onCreate: (Database db, int version) async {
await db.execute('create table $WATCHED_VIDEOS ($VIDEO_ID text,$REACHED_SECOND integer default 0,$NOTE text)');
await db.execute('create table $TABLE_FAVORITE_VIDEOS ($VIDEO_TITLE text,$VIDEO_ID text,$VIDEO_DESCRIPTION text,$VIDEO_THUMBURL text)');
await db.execute('create table $CHANNELS ($ID integer ,$NAME text,$LINK text, $PICTURE_LINK text,$VIEWED integer default 0,$GET_NOTIFICATIONS integer default 1,$LAST_VIDEO_TITLE text)');
},
onUpgrade: (db, oldVersion, newVersion)async {
var tableColumns= await db.query('PRAGMA table_info($WATCHED_VIDEOS)');
bool noteColumnExists=false;
for(int c=0;c<tableColumns.length;c++){
if(tableColumns[c]['name'].toString()==NOTE)noteColumnExists=true;
}
if(!noteColumnExists) await db.execute('alter table $WATCHED_VIDEOS add $NOTE text');
},);
*/
}
AppRouterDelegate _routerDelegate = AppRouterDelegate();
AppRouteInformationParser _routeInformationParser =
AppRouteInformationParser();
#override
void initState() {
super.initState();
initialiseNotification();
initialiseTimeZone();
showDailyReminderNotification();
configureDailyNewVideosFitch();
}
void configureDailyNewVideosFitch()async{
await AndroidAlarmManager.periodic(const Duration(days: 1),DAILY_NEW_VIDEOS_FETCH_ALARAM_ID , ()async{
Database d=RepositoryProvider.of<Database>(context);
List<Channel>subscribedChannels=await (d.select($ChannelsTable(d))..where((tbl) => tbl.getNotifications.equals(1))).get();
String channelsIds='';
for(int i=0;i<subscribedChannels.length;i++)channelsIds+=subscribedChannels[i].link+"/";
Map<String,String> map=Map();
map['channels']=channelsIds;
Post p=Post(context,'getVideosFromTime.php',map);
await p.fetchPost();
if(p.connectionSucceed){
dynamic resultJson=json.decode(p.result);
if(resultJson['result']=='success'){
for(int i=0;i<resultJson['data'].length;i++){
dynamic videoJson=resultJson['data'][i];
await flutterLocalNotificationsPlugin.show(videoJson['id'], videoJson['channelName'], videoJson['title'],
NotificationDetails(
android: AndroidNotificationDetails(
APP_NAME,
APP_NAME, NEW_VIDEO_AVAILABLE,
icon: '#mipmap/ic_launcher',
ticker: videoJson['title']
),
),
payload:'/watch?v='+videoJson['videoId']
);
}
}
}
},rescheduleOnReboot: true);
}
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
void showDailyReminderNotification()async{
const AndroidNotificationDetails androidPlatformChannelSpecifics =
AndroidNotificationDetails(APP_NAME,
APP_NAME, 'dailyReminder'+APP_NAME,
icon: '#mipmap/ic_launcher',
ticker: TIME_TO_LEARN);
const NotificationDetails platformChannelSpecifics =
NotificationDetails(android: androidPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.periodicallyShow(DAILY_REMINDER_NOTIFICATION_ID, TIME_TO_LEARN,
TIME_TO_LEARN_DESCRIPTION, RepeatInterval.daily, platformChannelSpecifics,
androidAllowWhileIdle: false);
}
void initialiseTimeZone()async{
tz.initializeTimeZones();
tz.setLocalLocation(tz.getLocation(await FlutterNativeTimezone.getLocalTimezone()));
}
void initialiseNotification()async{
flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin();
// initialise the plugin. app_icon needs to be a added as a drawable resource to the Android head project
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('#mipmap/ic_launcher');
final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(
/*onDidReceiveLocalNotification: onDidReceiveLocalNotification*/);
final MacOSInitializationSettings initializationSettingsMacOS =
MacOSInitializationSettings();
final InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsIOS,
macOS: initializationSettingsMacOS);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: selectNotification);
await flutterLocalNotificationsPlugin.cancel(DAILY_REMINDER_NOTIFICATION_ID);
}
Future selectNotification(String payload) async {
if(payload!=null)Navigator.pushNamed(context, payload);
}
#override
Widget build(BuildContext context) {
return DynamicTheme(
defaultBrightness: Brightness.light,
data: (brightness) => ThemeData(
primarySwatch: PRIMARY_COLOR,
brightness: brightness
),
themedWidgetBuilder: (context, data) {
return RepositoryProvider<Database>(
create:(context)=>mobile.constructDb() ,
child: BlocProvider(
create: (context){
//final db = RepositoryProvider.of<Database>(context);
//return AppBloc(db);
},
child: MaterialApp.router(
//title: 'Books App',
theme: data,
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
)
)
);
}
);
}
}
MobileAdTargetingInfo targetingInfo = MobileAdTargetingInfo(
// ignore: deprecated_member_use
gender: MobileAdGender.unknown,
childDirected: true
);
class AdmobAdd extends StatelessWidget{
#override
Widget build(BuildContext context) {
return !PRO?AdmobBanner(
adUnitId: kReleaseMode?BANNER_AD_UNIT_ID:BannerAd.testAdUnitId,
adSize: AdmobBannerSize.BANNER,
):Container();
}
}
InterstitialAd myInterstitial = InterstitialAd(
adUnitId: InterstitialAd.testAdUnitId,
targetingInfo: targetingInfo,
);
this is the source code of the routes configuration file
import 'package:catcher/catcher.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:videos/home.dart';
import 'package:videos/video.dart';
class AppRouteInformationParser extends RouteInformationParser<AppRoutePath> {
#override
Future<AppRoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location);
// Handle '/'
if (uri.pathSegments.length == 0) {
return AppRoutePath.home();
}
// Handle '/watch?v=fsdfsdfsd
if (uri.pathSegments.length == 1) {
if (!uri.pathSegments[0].startsWith('/watch?')) return AppRoutePath.unknown();
if(uri.pathSegments[0].startsWith('/watch?v=')){
var videoId=uri.queryParameters['v'];
return AppRoutePath.video(videoId);
/*var remaining = uri.pathSegments[1];
var id = int.tryParse(remaining);
if (id == null) return AppRoutePath.unknown();
return AppRoutePath.details(id);*/
}
}
// Handle unknown routes
return AppRoutePath.unknown();
}
#override
RouteInformation restoreRouteInformation(AppRoutePath path) {
if (path.isUnknown) {
return RouteInformation(location: '/404');
}
if (path.isHomePage) {
return RouteInformation(location: '/');
}
if(path.isVideoPage){
return RouteInformation(location: '/watch?v=${path.id}');
}
if (path.isDetailsPage) {
return RouteInformation(location: '/book/${path.id}');
}
return null;
}
}
class AppRouterDelegate extends RouterDelegate<AppRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoutePath> {
final GlobalKey<NavigatorState> navigatorKey;
//Book _selectedBook;
bool show404 = false;
String videoId;
/*List<Book> books = [
Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
Book('Foundation', 'Isaac Asimov'),
Book('Fahrenheit 451', 'Ray Bradbury'),
];*/
AppRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
AppRoutePath get currentConfiguration {
if (show404) {
return AppRoutePath.unknown();
}
if(videoId!=null) return AppRoutePath.video(videoId);
/*return _selectedBook == null
? AppRoutePath.home()
: AppRoutePath.details(books.indexOf(_selectedBook));*/
return AppRoutePath.home();
}
#override
Widget build(BuildContext context) {
return Navigator(
key: Catcher.navigatorKey,
pages: [
MaterialPage(
key: ValueKey('Home'),
child: MyHomePage()
),
if (show404)
MaterialPage(key: ValueKey('Unknown'), child: UnknownScreen())
else if(videoId!=null) VideoPageRoute(videoId: videoId)
/*else if (_selectedBook != null)
BookDetailsPage(book: _selectedBook)*/
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
//_selectedBook = null;
videoId=null;
show404 = false;
notifyListeners();
return true;
},
);
}
#override
Future<void> setNewRoutePath(AppRoutePath path) async {
if (path.isUnknown) {
//_selectedBook = null;
videoId=null;
show404 = true;
return;
}
/*if (path.isDetailsPage) {
if (path.id < 0 || path.id > books.length - 1) {
show404 = true;
return;
}
_selectedBook = books[path.id];
} else {
_selectedBook = null;
}*/
show404 = false;
}
void videoNotFound(){
videoId=null;
show404=true;
notifyListeners();
}
/*void _handleBookTapped(Book book) {
_selectedBook = book;
notifyListeners();
}*/
}
class AppRoutePath {
final String id;
final bool isUnknown;
AppRoutePath.home()
: id = null,
isUnknown = false;
AppRoutePath.details(this.id) : isUnknown = false;
AppRoutePath.video(this.id): isUnknown=false;
AppRoutePath.unknown()
: id = null,
isUnknown = true;
bool get isHomePage => id == null;
bool get isDetailsPage => id != null;
bool get isVideoPage => id!=null;
}
/*class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
#required this.books,
#required this.onTapped,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
for (var book in books)
ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () => onTapped(book),
)
],
),
);
}
}
*/
/*class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
#required this.book,
});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) ...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
],
),
),
);
}
}
*/
class UnknownScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('404!'),
),
);
}
}
The problem is that I have several screens that has no specific url and I do not want to provide them with routes, I want simply to use Navigator.push()
That is working fine, but when pressing the back button or when I use Navigator.pop() the app closes entirely instead of navigating back to the previous page
How can I use Navigator.pushNamed only for named routes and use Navigator.push for other pages without causing the problem to occur
Thanks in advance
I have a REST API which allows the user to update a Book model
GET /api/books.json # list of books
PUT /api/books/1.json # update the book with id=1
I have corresponding screens for these actions (an Index screen to list books; an Edit screen to edit the book details) in my flutter application. When creating the form edit a Book,
I pass a Book object to the Edit form
In the Edit form, I make a copy of the book object. I create a copy and not edit the original object to ensure that the object is not changed if the Update fails at the server
If the update is successful, I display an error message.
However, when I go back to the Index view, the book title is still the same (as this object has not changed). Also, I found that even if I make changes to the original object, instead of making a copy, the build method is not called when I go 'back'. I am wondering if there is a pattern that I can use to have this object updated across the application on successful updates.
I have the following classes
class Book {
final int id;
final String title;
Book(this.id, this.title);
static Book fromJson(json) {
return Book(
json['id'],
json['title']);
}
Map<String, dynamic> toJson() => {
'title': title
};
Future<bool> update() {
var headers = {
'Content-Type': 'application/json'
};
return http
.put(
"$HOST/api/books/${id}.json",
headers: headers,
body: jsonEncode(this.toJson()),
)
.then((response) => response.statusCode == 200);
}
}
Here is the Index view
class BooksIndex extends StatefulWidget {
static final tag = "books-index";
#override
_BooksIndexState createState() => _BooksIndexState();
}
class _BooksIndexState extends State<BooksIndex> {
final Future<http.Response> _getBooks = http.get("$HOST/api/books.json", headers: headers);
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getBooks,
builder: (context, snapshot) {
if (snapshot.hasData) {
var response = snapshot.data as http.Response;
if (response.statusCode == 200) {
List<dynamic> booksJson = jsonDecode(response.body);
List<Book> books = booksJson.map((bookJson) {
return Book.fromJson(bookJson);
}).toList();
return _buildMaterialApp(ListView.builder(
itemCount: books.length,
itemBuilder: (context, index) {
var book = books[index];
return ListTile(
title: Text(book.title),
onTap: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) => BooksEdit(book: book)
));
},
);
},
));
} else {
return _buildMaterialApp(Text(
"An error occured while trying to retrieve the books. Status=${response.statusCode}"));
}
} else if (snapshot.hasError) {
return _buildMaterialApp(Text(
"Could not load books. Please check your internet connection."));
} else {
return _buildMaterialApp(Text("Loading"));
}
});
}
_buildMaterialApp(widget) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Books"),
),
body: widget,
),
);
}
}
Here is the Edit form
class BooksEdit extends StatelessWidget {
final Book book;
BooksEdit({Key key, #required this.book}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Edit ${book.title}"),
),
body: Column(
children: <Widget>[
BookForm(
book: book,
)
],
),
);
}
}
class BookForm extends StatefulWidget {
Book book;
BookForm({Key key, #required this.book}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _BookFormState();
}
}
class _BookFormState extends State<BookForm> {
TextEditingController _titleField;
RaisedButton _submitBtn;
bool isError = false;
String formMessage = "";
#override
Widget build(BuildContext context) {
_titleField = TextEditingController(text: widget.book.title);
_submitBtn = RaisedButton(
child: Text(
"Update",
style: Theme
.of(context)
.textTheme
.button,
),
color: Theme
.of(context)
.primaryColor,
onPressed: () {
var book = Book(
widget.book.id,
_titleField.text
);
book.update().then((success) {
if (success) {
setState(() {
isError = false;
formMessage = "Successfully updated";
widget.book = book;
});
} else {
setState(() {
isError = true;
formMessage = "Book could not be updated";
});
}
}, onError: (error) {
setState(() {
isError = true;
formMessage =
"An unexpected error occured. It has been reported to the administrator.";
});
});
},
);
var formMessageColor = isError ? Colors.red : Colors.green;
return Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
formMessage,
style: TextStyle(color: formMessageColor),
),
TextFormField(
controller: _titleField,
),
_submitBtn
],
),
);
}
}
Here the main file
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final routes = <String, WidgetBuilder>{
'/': (context) => BooksIndex(),
};
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "BooksApp",
theme: ThemeData(primarySwatch: Colors.green),
routes: routes,
initialRoute: '/',
);
}
}
ALSO, I am new to Flutter. So, I would appreciate it if I get any feedback about any other places in my code that I can improve upon.
You can copy paste run full code below
I use fixed json string to simulate http, when update be called, only change json string
You can also reference official example https://flutter.dev/docs/cookbook/networking/fetch-data
Step 1 : You can await Navigator.push and do setState after await to refresh BooksIndex
Step 2 : Move parse json logic to getBooks
code snippet
return ListTile(
title: Text(book.title),
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BooksEdit(book: book)));
setState(() {});
},
Future<List<Book>> httpGetBooks() async {
print("httpGetBooks");
var response = http.Response(jsonString, 200);
if (response.statusCode == 200) {
print("200");
List<dynamic> booksJson = jsonDecode(response.body);
List<Book> books = booksJson.map((bookJson) {
return Book.fromJson(bookJson);
}).toList();
print(books[1].title.toString());
return books;
}
}
working demo
full code
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final routes = <String, WidgetBuilder>{
'/': (context) => BooksIndex(),
};
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "BooksApp",
theme: ThemeData(primarySwatch: Colors.green),
routes: routes,
initialRoute: '/',
);
}
}
class BooksEdit extends StatelessWidget {
final Book book;
BooksEdit({Key key, #required this.book}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Edit ${book.title}"),
),
body: Column(
children: <Widget>[
BookForm(
book: book,
)
],
),
);
}
}
class BookForm extends StatefulWidget {
Book book;
BookForm({Key key, #required this.book}) : super(key: key);
#override
State<StatefulWidget> createState() {
return _BookFormState();
}
}
class _BookFormState extends State<BookForm> {
TextEditingController _titleField;
RaisedButton _submitBtn;
bool isError = false;
String formMessage = "";
#override
Widget build(BuildContext context) {
_titleField = TextEditingController(text: widget.book.title);
_submitBtn = RaisedButton(
child: Text(
"Update",
style: Theme.of(context).textTheme.button,
),
color: Theme.of(context).primaryColor,
onPressed: () {
var book = Book(widget.book.id, _titleField.text);
book.update().then((success) {
if (success) {
setState(() {
isError = false;
formMessage = "Successfully updated";
widget.book = book;
});
} else {
setState(() {
isError = true;
formMessage = "Book could not be updated";
});
}
}, onError: (error) {
setState(() {
isError = true;
formMessage =
"An unexpected error occured. It has been reported to the administrator.";
});
});
},
);
var formMessageColor = isError ? Colors.red : Colors.green;
return Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
formMessage,
style: TextStyle(color: formMessageColor),
),
TextFormField(
controller: _titleField,
),
_submitBtn
],
),
);
}
}
class BooksIndex extends StatefulWidget {
static final tag = "books-index";
#override
_BooksIndexState createState() => _BooksIndexState();
}
String jsonString = '''
[{
"id" : 1,
"title" : "t"
}
,
{
"id" : 2,
"title" : "t1"
}
]
''';
class _BooksIndexState extends State<BooksIndex> {
Future<List<Book>> httpGetBooks() async {
print("httpGetBooks");
var response = http.Response(jsonString, 200);
if (response.statusCode == 200) {
print("200");
List<dynamic> booksJson = jsonDecode(response.body);
List<Book> books = booksJson.map((bookJson) {
return Book.fromJson(bookJson);
}).toList();
print(books[1].title.toString());
return books;
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
print("build ${jsonString}");
return FutureBuilder<List<Book>>(
future: httpGetBooks(),
builder: (context, snapshot) {
if (snapshot.hasData) {
print("hasData");
return _buildMaterialApp(ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
var book = snapshot.data[index];
print(book.title);
return ListTile(
title: Text(book.title),
onTap: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BooksEdit(book: book)));
setState(() {});
},
);
},
));
} else if (snapshot.hasError) {
return _buildMaterialApp(Text(
"Could not load books. Please check your internet connection."));
} else {
return _buildMaterialApp(Text("Loading"));
}
});
}
_buildMaterialApp(widget) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Books"),
),
body: widget,
),
);
}
}
class Book {
final int id;
final String title;
Book(this.id, this.title);
static Book fromJson(json) {
return Book(json['id'], json['title']);
}
Map<String, dynamic> toJson() => {'title': title};
Future<bool> update() {
print("update");
var headers = {'Content-Type': 'application/json'};
/*return http
.put(
"$HOST/api/books/${id}.json",
headers: headers,
body: jsonEncode(this.toJson()),
)
.then((response) => response.statusCode == 200);*/
jsonString = '''
[{
"id" : 1,
"title" : "t"
}
,
{
"id" : 2,
"title" : "test"
}
]
''';
return Future.value(true);
}
}
setState(() {
});
},
);