Not able to cancel UPI payments Razorpay Flutter - flutter

I'm using razorpay_flutter, to integrate Payments, my problem is, if I select UPI as a payment option, and while the payment is getting processed, if I cancel the payment, it still goes in PaymentSuccessResponse. According to documentation of razorpay, if Payment is cancelled by user means, it should go in PaymentFailureResponse.
Here's my code:
class CheckRazor extends StatefulWidget {
const CheckRazor({Key key}) : super(key: key);
#override
_CheckRazorState createState() => _CheckRazorState();
}
class _CheckRazorState extends State<CheckRazor> {
Razorpay _razorpay = Razorpay();
var options;
Future payData() async {
try {
_razorpay.open(options);
} catch (e) {
print(e);
}
_razorpay.on(Razorpay.EVENT_PAYMENT_SUCCESS, _handlePaymentSuccess);
_razorpay.on(Razorpay.EVENT_PAYMENT_ERROR, _handlePaymentError);
_razorpay.on(Razorpay.EVENT_EXTERNAL_WALLET, _handleExternalWallet);
}
void _handlePaymentSuccess(PaymentSuccessResponse response) async {
print(response);
var message = response.paymentId;
Navigator.pushReplacementNamed(context, Routes.PaymentSuccess,
arguments: widget.details);
_razorpay.clear();
}
void _handlePaymentError(PaymentFailureResponse response) {
var message = response.message;
Map res = json.decode(message);
Map reason = res["error"];
String finalReason = reason["reason"];
if(finalReason == "payment_cancelled") {
Navigator.of(context).pop(context);
_razorpay.clear();
}
else if(finalReason == "payment_failed") {
Navigator.pushReplacementNamed(context, Routes.PaymentFailure,
arguments: response);
_razorpay.clear();
}
}
void _handleExternalWallet(ExternalWalletResponse response) {
_razorpay.clear();
}
#override
void initState() {
super.initState();
options = {
'key':
// "rzp_test_11111111111", // Enter the Live Key ID generated from the Dashboard
"rzp_test_OCp8bDk51p2f96",
'amount': 100,
'name': 'Test',
'currency': "INR",
'description': 'Fees',
'prefill': {
'contact': 8888888888,
'email': test#email.com,
},
'external': {
'wallets': ['paytm']
}
};
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: payData(),
builder: (context, snapshot) {
return Container(
child: Center(
child: Text(
"Loading...",
style: TextStyle(
fontSize: 20,
),
),
),
);
}),
);
}
}

Related

How to fix eternal loading when fetching data in flutter?

I am trying to make sign in in dart using cubit/bloc.
And when I try to change state of my cubit(auth) to Waiting => CircularProgressIndicator
Then my app is getting stuck when api returns statusCode == 400 and I call another emit to show dialog window
But if sign in is successful(statusCode == 200) then everything is ok, and I navigate to main_page.dart
How to fix this problem and transfer data reponse from api to widget(I want to send statusCode and message to widget)
My cubit file:
class AuthCubit extends Cubit<AuthState> {
AuthCubit() : super(AuthState(
email: "",
password: "",
));
final ApiService _apiService = ApiService();
void setEmail(String email) => emit(state.copyWith(email: email));
void setPassword(String password) => emit(state.copyWith(password: password));
Future signIn() async {
emit(WaitingSignInAuth(state.email, state.password));
try {
final data = await _apiService.signIn(
email: state.email ?? "",
password: state.password ?? ""
);
if (data['success']) {
print(data);
emit(const SuccessAutentification());
}
} on DioError catch (ex) {
print(ex.toString());
//I want to transfer to ErrorSignInAuth ex.response!.data['message'] but I don't know how
emit(ErrorSignInAuth(state.email, state.password));
} catch (e) {
print(e);
}
}
}
This is my state file:
class AuthState extends Equatable {
final String? email;
final String? password;
const AuthState({
this.email,
this.password,
});
AuthState copyWith({
String? email,
String? password,
}) {
return AuthState(
email: email ?? this.email,
password: password ?? this.password,
);
}
#override
List<Object?> get props =>
[email, password];
}
class SuccessAutentification extends AuthState {
const SuccessAutentification() : super();
}
class WaitingSignInAuth extends AuthState {
const WaitingSignInAuth(email, password) : super(email: email, password: password);
}
class ErrorSignInAuth extends AuthState {
const ErrorSignInAuth(email, password) : super(email: email, password: password);
}
And this is the widget where I use this cubit:
#override
Widget build(BuildContext context) {
return BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
if (state is WaitingSignInAuth) {
showDialog(
context: context,
builder: (context) => Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.black.withOpacity(0.6),
child: const Center(
child: CircularProgressIndicator(
strokeWidth: 1,
color: Colors.black,
backgroundColor: Colors.white,
),
),
));
}
if (state is SuccessAutentification) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => const MainWidget(),
),
);
}
if (state is ErrorAuthentification) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Bad request"),
content: SingleChildScrollView(
child: ListBody(
children: <Widget>[
Text("Error") // I want to show here error message
],
),
),
actions: <Widget>[
TextButton(
child: const Text("Close"),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
}
);
}
},
builder: (context, state) {
return Scaffold(
LoginButton(
color: _isValid ? AppColors.white : AppColors.whiteA3A3A3,
text: "Login in",
textColor: Colors.black,
isButtonDisabled: _isValid ? false : true,
onPressed: () {
if (_key.currentState!.validate()) {
BlocProvider.of<AuthCubit>(context).setEmail(_email.text.trim());
BlocProvider.of<AuthCubit>(context).setPassword(_password.text.trim());
BlocProvider.of<AuthCubit>(context).signIn();
_key.currentState!.save();
}
},
)
);
},
);
}
In signIn() function, after you emit WaitingSignInAuth state, in some cases you are not emitting any other state, so your page is always loading.
Future signIn() async {
emit(WaitingSignInAuth(state.email, state.password));
try {
final data = await _apiService.signIn(
email: state.email ?? "",
password: state.password ?? ""
);
if (data['success']) {
print(data);
emit(const SuccessAutentification());
} else {
// emit state
emit(ErrorSignInAuth(state.email, state.password));
}
} on DioError catch (ex) {
print(ex.toString());
emit(ErrorSignInAuth(state.email, state.password));
} catch (e) {
print(e);
// emit state
emit(ErrorSignInAuth(state.email, state.password));
}
}

Flutter app login after a logout PostgresSQLException

I'm working on a Flutter app using Riverpod for the state management and Postgres for the database. I try to do a simple thing: display a message in the home screen with the nickname of the current user. When I logout and login to check if my feature works, my user is null and my console display a PostgresSQLException :
PostgreSQLSeverity.error : Attempting to reopen a closed connection. Create a instance instead.
Any idea where I made a mistake?
My user_repository:
PostgreSQLConnection connection = PostgreSQLConnection(
'10.0.2.2', 5432, DatabaseAccess.databaseName,
queryTimeoutInSeconds: 3600,
timeoutInSeconds: 3600,
username: DatabaseAccess.databaseUser,
password: DatabaseAccess.databasePassword);
Future<AppUser?> getCurrentUser() async {
try {
await connection.open();
final result = await connection.mappedResultsQuery(
'select * from public.user where email = #emailValue',
substitutionValues: {
'emailValue': email,
},
allowReuse: true,
timeoutInSeconds: 30,
);
final userFromDataBase = result[0]['user']!;
return AppUser(
email: userFromDataBase['email'],
nickname: userFromDataBase['nickname'],
role: userFromDataBase['role'],
firstname: userFromDataBase['firstname'],
lastname: userFromDataBase['lastname'],
);
} on PostgreSQLException catch(e) {
print(ErrorHandler(message: e.toString()));
return null;
}
}
My screen:
class HomeScreen extends HookConsumerWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
final currentUser = ref.watch(currentUserProvider);
return Scaffold(
body: currentUser.when(
data: (user) => _buildBody(context, user, ref),
loading: () => const Center(child: CircularProgressIndicator()),
error: (error, _) => _errorBody(context, ref),
)
);
}
Widget _buildBody(BuildContext context, AppUser? user, WidgetRef ref) {
if(user == null) {
return _errorBody(context, ref);
} else {
return Center(child: Text(
'Welcome ${user.getNickname}',
style: const TextStyle(fontSize: 20),
));
}
}
Widget _errorBody(BuildContext context, WidgetRef ref) {
return const Center(child: Text(
"Error: No user found",
style: TextStyle(fontSize: 20, color: Colors.red),
));
}
}
I resolved this issue with the following code. It wasn't a postgres issue but a dark story about riverpod's providers.
My providers.dart :
final futureCurrentUserProvider = Provider<Future<AppUser?>>((ref) {
return UserRepository().getCurrentUser(ref.watch(emailChangeProvider));
});
final currentUserProvider = FutureProvider.autoDispose<AppUser?>((ref) => UserRepository().getCurrentUser(ref.watch(emailChangeProvider)));
final authChangeProvider = StreamProvider<User?>((ref) {
return ref.read(authRepositoryProvider).authUserChange;
});
final emailChangeProvider = Provider<String?>((ref) {
return ref.watch(authChangeProvider).value?.email;
});

The element type 'Future<List<Organization>>?' can't be assigned to the list type 'Widget'

class Organization_Api{
static Future<List<dynamic>> getData(
{required String target, String? limit}) async {
try {
var uri = Uri.https(
BASE_URL,
"api/$target",
target == "organizations"
? {
"offset": "0",
"limit": limit,
}
: {});
var response = await http.get(uri);
var data = jsonDecode(response.body);
List tempList = [];
if (response.statusCode != 200) {
throw data["message"];
}
for (var v in data) {
tempList.add(v);
}
return tempList;
} catch (error) {
log("An error occured $error");
throw error.toString();
}
}
static Future<List<Organization>> getAllOrganizations(
{required String limit}) async {
List temp = await getData(
target: "organizations",
limit: limit,
);
return Organization.organizationsToList(temp);
}
static Future<Organization> getOrganizationById({required String id}) async {
try {
var uri = Uri.https(
BASE_URL,
"api/organizations/$id",
);
var response = await http.get(uri);
var data = jsonDecode(response.body);
if (response.statusCode != 200) {
throw data["message"];
}
return Organization.fromJson(data);
} catch (error) {
log("an error occured while getting organization info $error");
throw error.toString();
}
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
static String routeName = "/home";
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Future<List<Organization>>? result ;
void initState(){
result = Organization_Api.getAllOrganizations(limit: '4');
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Organizations", style: TextStyle(color: Colors.black),
),
backgroundColor: Colors.white,
centerTitle: true,
),
body: Padding(
padding: EdgeInsets.all(10.0),
child: Column(
children:
<Widget>[
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children:<Widget>[
ListView(
shrinkWrap: true,
children:<Widget> [result],
)
],
),
)
],
),
),
);
}
}
class Organization{
final int OrganizationId;
final String OrganizationName;
Organization({required this.OrganizationId,required this.OrganizationName});
factory Organization.fromJson(Map<String,dynamic> json){
return Organization(OrganizationId: json['OrganizationId'], OrganizationName: json['OrganizationName']);
}
Map toJson(){
return{
"OrganizationId": this.OrganizationId,
"OrganizationName": this.OrganizationName,
};
}
static List<Organization> organizationsToList(List organizationToList) {
return organizationToList.map((data) {
return Organization.fromJson(data);
}).toList();
}
}
Error = The element type >'Future<List>?' can't be assigned to the list type 'Widget'.
I just want to check the data coming from the service, but I couldn't find how to do it.
What did I do wrong or what did I miss to list the incoming data?
I shared the screen page and the codes on how I got the information from the service.
Your Organization_Api.getAllOrganizations provide a future. You can use FutureBuilder.
class _HomeScreenState extends State<HomeScreen> {
Future<List<Organization>>? myFuture;
#override
void initState() {
myFuture = Organization_Api.getAllOrganizations(limit: '4');
super.initState();
}
And on future builder
FutureBuilder<List<Organization>?>(
future: myFuture,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
return ListView(
shrinkWrap: true,
//children: snapshot.data!, // when `Organization` is a widget
children:// when `Organization` is a data model class
snapshot.data!.map((e) => Text(e.toString())).toList(),
);
}
return CircularProgressIndicator();
},
)
Also check Randal L. Schwartz video on using Future

Code doesn't even enter the onIceCandiate() while answering the SDP for webRTC in flutter

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!

How to sync a resource or a model object across different screens/widgets when it is updated?

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(() {
});
},
);