How to send dynamic url from DataProvider to PDF Flutter - flutter

I'm new to Flutter. I have DataProvider which consists of Pdf data like it's Title, pdfURL and etc and I created a ListView on that ListView I have some items.
Whenever I click on any item it should open specified pdf url on PDF VIEWER. I want to pass that data dynamically to the getFileFromUrl; how should I pass that data.
This is my DataProvider class:
class DataProvider with ChangeNotifier{
List<PdfBook> _pdfItems = [
PdfBook(
id: 'p1',
title: 'PDF Bookmark Sample',
pdfUrl: 'https://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf',
avatar: 'T',
),
PdfBook(
id: 'p2',
title: 'PDF 995',
pdfUrl: 'http://www.pdf995.com/samples/pdf.pdf',
avatar: 'T2',
),
];
List<PdfBook> get pdfItems{
return [..._pdfItems];
}
PdfBook findById(String id){
return _pdfItems.firstWhere((item) => item.id == id);
}
}
This is my PdfViewState:
class PdfItem extends StatefulWidget {
#override
_PdfItemState createState() => _PdfItemState();
}
class _PdfItemState extends State<PdfItem> {
String assetPDFPath = "";
String urlPDFPath = "";
#override
void initState() {
super.initState();
getFileFromAsset("assets/mypdf.pdf").then((f) {
setState(() {
assetPDFPath = f.path;
print(assetPDFPath);
});
});
getFileFromUrl("http://www.pdf995.com/samples/pdf.pdf").then((f) {
setState(() {
urlPDFPath = f.path;
print(urlPDFPath);
});
});
}
Future<File> getFileFromAsset(String asset) async {
try {
var data = await rootBundle.load(asset);
var bytes = data.buffer.asUint8List();
var dir = await getApplicationDocumentsDirectory();
File file = File("${dir.path}/mypdf.pdf");
File assetFile = await file.writeAsBytes(bytes);
return assetFile;
} catch (e) {
throw Exception("Error opening asset file");
}
}
Future<File> getFileFromUrl(String url) async {
try {
var data = await http.get(url);
var bytes = data.bodyBytes;
var dir = await getApplicationDocumentsDirectory();
File file = File("${dir.path}/mypdfonline.pdf");
File urlFile = await file.writeAsBytes(bytes);
return urlFile;
} catch (e) {
throw Exception("Error opening url file");
}
}
#override
Widget build(BuildContext context) {
final pdf = Provider.of<PdfBook>(context, listen: false);
return ListTile(
title: Text(pdf.title),
onTap: () {
if (urlPDFPath != null) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
PdfViewPage(path: urlPDFPath)));
}
},
leading: CircleAvatar(
child: Text(pdf.avatar),
backgroundColor: Theme.of(context).accentColor,
),
trailing: Icon(Icons.arrow_right),
);
}
}
This is my ListView Class:
class PdfListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
final pdfData = Provider.of<DataProvider>(context);
final pdf = pdfData.pdfItems;
return Scaffold(
appBar: AppBar(
title: Text("PDF Books"),
),
body: ListView.builder(
itemCount: pdf.length,
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: pdf[i],
child: PdfItem(),
),
),
);
}
}

Related

Linear Progress indicator could not manage its state while downloading files

I am new to Flutter and am currently working on a Flutter app that downloads files from given links. The issue is that when only one link app is working fine but when more than one link arrive file does not download. I receive the link data in json file then I parsed the json and used these links inside the listview.builder. I tried very much but could not find the solution. Here is the code for the screen. Main issue is that I think state is not managed well.
dsharing.json file contain name and links of files
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import '../services/services.dart';
class ReceiveScreen extends StatefulWidget {
const ReceiveScreen({Key? key, }) : super(key: key);
#override
State<ReceiveScreen> createState() => _ReceiveScreenState();
}
class _ReceiveScreenState extends State<ReceiveScreen> {
Map<int, double> progress = {};
int received = 0;
int total = 0;
var _url = "http://192.168.1.100:50500";
List<Receiver> receivers = [];
#override
initState()
{
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Receive Files"),
) ,
body: Column(
children: [
ElevatedButton(
onPressed: ()async
{
await getUrls();
},
child: const Text("Get Response")
),
Text(rsponse),
FutureBuilder(
future: getUrls(),
builder: (context, AsyncSnapshot<Files> snapshot)
{
if(!snapshot.hasData)
{
return const Center(child: CircularProgressIndicator());
}
Files files = snapshot.data!;
return ListView.builder(
itemCount: files.names!.length,
shrinkWrap: true,
itemBuilder: (context, index)
{
return Column(
children: [
ElevatedButton(
onPressed: () async
{
final path = "/storage/emulated/0/Download/${files.names![index]}";
await Dio().download(
files.urls![index],
path,
onReceiveProgress: (received, total)
{
progress[index] = received/total;
}
);
},
child: Text("${files.names![index] } Download")
),
LinearProgressIndicator(
value: progress[index],
minHeight: 20,
)
],
);
}
);
}
)
],
)
);
}
String rsponse = "";
fetchDataApi() async
{
var response = await get(Uri.parse("$_url/dsharing.json"));
if(response.statusCode == 200)
{
return jsonDecode(response.body);
}
}
Future<Files> getUrls() async
{
var data = await fetchDataApi();
return Files.fromJson(data, _url);
}
}
class Files
{
final List? names;
final List? urls;
Files({ this.names, this.urls});
factory Files.fromJson(Map map, String url)
{
var fileNames = [];
String name = map['name'];
if(name.contains(":"))
{
int index = name.indexOf(":") + 1;
name = name.substring(index + 1, name.length);
var x = name.replaceAll(" ", '^*&');
fileNames = x.split("^*&").toList();
}
else
{
fileNames.add(name);
}
var x = map['data'].toString().split("|dsharing|").toList();
var fileUrls = x.map((i) => url + "?/q="+ i).toList();
return Files(
names: fileNames,
urls: fileUrls
);
}
}

Flutter| Dio : Invalid argument(s) (value): Must not be null

i am using shared preference to pass data from one screen to other, it was working perfectly few days ago, but now it is sending this error
Invalid argument(s) (value): Must not be null
i'm calling login api, it is running sucessfully, i am getting api data on its response but it goes to catch part after print the data, it is not navigating to other screen, here is the login function code
SharedPreferences myPrefs;
Future login() async {
Dio dio = new Dio();
try {
data = {
'username':"Munib khatri",
'password':"Munib123",
'date': formattedDate
};
await dio
.post(localhostUrlLogin, data: json.encode(data),)
.then((onResponse) async {
print(onResponse.data);
String name = (onResponse.data['User']['username']);
String email = (onResponse.data['User']['email']);
String designation = (onResponse.data['User']['Designation']);
String token = (onResponse.data['AccessToken']);
//here i am calling another api
data={
"token":token,
"username":name,
"leave":"Approved",
"type":"maternity"
};
dio
.post(localHostUrlleaveCount, data: json.encode(data))
.then((onResponse) async {
int sickCount=(onResponse.data['sick']);
int annualCount=(onResponse.data['annual']);
await myPrefs.setInt('sickCount', sickCount);
await myPrefs.setInt('annualCount', annualCount);
}).catchError((onerror){
});
await myPrefs.setString('name', name);
await myPrefs.setString('email', email);
await myPrefs.setString('designation', designation);
Navigator.push(
context, new MaterialPageRoute(builder: (context) => Navigation()));
});
} catch (e) {
print(e.toString());
}
}
Navigation:
class Navigation extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
debugShowCheckedModeBanner: false,
theme: new ThemeData(primaryColor: Colors.blue),
home: EmployeeNavigation(),
);
}
}
int _selectedTab = 0;
final _pageOptions = [Home(), location(), Profile()];
String getname = "";
String getemail = "";
String getdesignation = "";
String getaccesstoken = "";
// ignore: must_be_immutable
class EmployeeNavigation extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return EmployeeNavigationState();
}
}
class EmployeeNavigationState extends State<EmployeeNavigation> {
var email;
var designation;
var date;
bool valuefirst = false;
String backtext = "";
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_selectedTab == 0) {
return true;
}
setState(() {
_selectedTab = 0;
});
return false;
},
child: Scaffold(
drawer: NavigationDrawerWidget(), //this is a drawer file
body: _pageOptions[_selectedTab],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.blue[50],
type: BottomNavigationBarType.fixed,
currentIndex: _selectedTab,
onTap: (value) {
print(value);
setState(() {
_selectedTab = value;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(
icon: Icon(Icons.location_on), label: "Location"),
BottomNavigationBarItem(
icon: Icon(Icons.person),
label: "Profile",
),
],
)));
}
}
i am getting response of both api, but can't navigate to other screen.
myPrefs doesn't seem to be initialized !!!
Try:
Future login() async {
Dio dio = new Dio();
var myPrefs = await SharedPreferences.getInstance();
...
}

Error opening PDF file after downloading from server Flutter

Hello I have a screen in the app that downloads a PDF file and displays the download progress in percent. After downloading, the file is opened using the package of pdf_render: ^ 1.0.10. Sometimes it opens and displays the PDF file and sometimes it does not open the file and I have a blank screen. I have no fault in the console and I do not know what the fault is. Thank you
Flutter App :
class Web_view_vehicle extends StatefulWidget {
var choice ;
var name_vehicle;
Web_view_vehicle({ required this.choice , this.name_vehicle}) : super();
#override
_Web_view_vehicleState createState() => _Web_view_vehicleState();
}
class _Web_view_vehicleState extends State<Web_view_vehicle> {
bool downloading = false;
String progressString = "";
String path = '';
late List data ;
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/teste.pdf');
}
Future<File> writeCounter(Uint8List stream) async {
final file = await _localFile;
// Write the file
return file.writeAsBytes(stream);
}
Future<Uint8List> fetchPost() async {
Dio dio = Dio();
var response = await dio.get(widget.choice,
onReceiveProgress: (rec, total) {
print("Rec: $rec , Total: $total");
if(!mounted)return;
setState(() {
downloading = true;
progressString = ((rec / total) * 100).toStringAsFixed(0) + "%";
});
},options: Options(responseType: ResponseType.bytes));
final responseJson = response.data;
if (!mounted) {
}
setState(() {
downloading = false;
progressString = "Completed";
});
print("Download completed");
return responseJson;
}
loadPdf() async {
writeCounter((await fetchPost()));
path = (await _localFile).path;
if (!mounted) return;
setState(() {});
}
#override
void initState() {
super.initState();
loadPdf();
}
#override
Widget build(BuildContext context) {
Color? bgColor = Colors.grey[300];
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
leading: Padding(
padding: const EdgeInsets.fromLTRB(5.0 , 0.0 , 0.0 , 0.0),
child: new IconButton(
icon: new Icon(Icons.arrow_back),
onPressed: (){Navigator.pop(context,true);}
),
),
title: Text('מדריך לרכב ${widget.name_vehicle}',textAlign: TextAlign.center,textDirection: TextDirection.rtl,),
backgroundColor: Colors.red[400],
centerTitle: true,
actions: <Widget>[
],
),
body:
(path.length > 5)?
PdfViewer.openFile(path,
):
Center(child: Column(crossAxisAlignment: CrossAxisAlignment.center,mainAxisAlignment: MainAxisAlignment.center,mainAxisSize: MainAxisSize.max,
children: [
CircularProgressIndicator(),
Text('טוען מדריך ${progressString}',textDirection: TextDirection.rtl,textAlign: TextAlign.center,)
],
))
);
}
}

Download ZIP, Extract it and Show the images file but not load from Document Directory?

Hello friend I am creating project when user install my app first they download zip file from internet and extract all file inside it then show in my app but problem is that when user download and extracted file the image inside zip shown in app when I close app and reopen it again image not load from Document directory everytime they need click on download button then show the image in the app any one tell me what's issue here is code?
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
class DownloadAssetsDemo extends StatefulWidget {
DownloadAssetsDemo() : super();
final String title = "Download & Extract ZIP Demo";
#override
DownloadAssetsDemoState createState() => DownloadAssetsDemoState();
}
class DownloadAssetsDemoState extends State<DownloadAssetsDemo> {
//
bool _downloading;
String _dir;
List<String> _images, _tempImages;
String _zipPath = 'https://coderzheaven.com/youtube_flutter/images.zip';
String _localZipFileName = 'images.zip';
#override
void initState() {
super.initState();
_images = List();
_tempImages = List();
_downloading = false;
_initDir();
}
_initDir() async {
if (null == _dir) {
_dir = (await getApplicationDocumentsDirectory()).path;
}
}
Future<File> _downloadFile(String url, String fileName) async {
var req = await http.Client().get(Uri.parse(url));
var file = File('$_dir/$fileName');
return file.writeAsBytes(req.bodyBytes);
}
Future<void> _downloadZip() async {
setState(() {
_downloading = true;
});
_images.clear();
_tempImages.clear();
var zippedFile = await _downloadFile(_zipPath, _localZipFileName);
await unarchiveAndSave(zippedFile);
setState(() {
_images.addAll(_tempImages);
_downloading = false;
});
}
unarchiveAndSave(var zippedFile) async {
var bytes = zippedFile.readAsBytesSync();
var archive = ZipDecoder().decodeBytes(bytes);
for (var file in archive) {
var fileName = '$_dir/${file.name}';
if (file.isFile) {
var outFile = File(fileName);
//print('File:: ' + outFile.path);
_tempImages.add(outFile.path);
outFile = await outFile.create(recursive: true);
await outFile.writeAsBytes(file.content);
}
}
}
buildList() {
return Expanded(
child: ListView.builder(
itemCount: _images.length,
itemBuilder: (BuildContext context, int index) {
return Image.file(
File(_images[index]),
fit: BoxFit.fitWidth,
);
},
),
);
}
progress() {
return Container(
width: 25,
height: 25,
padding: EdgeInsets.fromLTRB(0.0, 20.0, 10.0, 20.0),
child: CircularProgressIndicator(
strokeWidth: 3.0,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
_downloading ? progress() : Container(),
IconButton(
icon: Icon(Icons.file_download),
onPressed: () {
_downloadZip();
},
),
],
),
body: Container(
child: Column(
children: <Widget>[
buildList(),
],
),
),
);
}
}
You can copy paste run full code below
You can save and restore List<String> with package SharedPreferences
code snippet
getHistoryImageList() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_images = prefs.getStringList("images");
});
}
...
Future<void> _downloadZip() async {
...
await unarchiveAndSave(zippedFile);
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setStringList("images", _tempImages);
setState(() {
_images = List<String>.from(_tempImages);
_downloading = false;
});
}
working demo
full code
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class DownloadAssetsDemo extends StatefulWidget {
DownloadAssetsDemo() : super();
final String title = "Download & Extract ZIP Demo";
#override
DownloadAssetsDemoState createState() => DownloadAssetsDemoState();
}
class DownloadAssetsDemoState extends State<DownloadAssetsDemo> {
//
bool _downloading;
String _dir;
List<String> _images, _tempImages;
String _zipPath = 'https://coderzheaven.com/youtube_flutter/images.zip';
String _localZipFileName = 'images.zip';
getHistoryImageList() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
_images = prefs.getStringList("images");
});
}
#override
void initState() {
super.initState();
_images = [];
getHistoryImageList();
_tempImages = List();
_downloading = false;
_initDir();
}
_initDir() async {
if (null == _dir) {
_dir = (await getApplicationDocumentsDirectory()).path;
print("init $_dir");
}
}
Future<File> _downloadFile(String url, String fileName) async {
var req = await http.Client().get(Uri.parse(url));
var file = File('$_dir/$fileName');
print("file.path ${file.path}");
return file.writeAsBytes(req.bodyBytes);
}
Future<void> _downloadZip() async {
setState(() {
_downloading = true;
});
_images?.clear();
_tempImages?.clear();
var zippedFile = await _downloadFile(_zipPath, _localZipFileName);
await unarchiveAndSave(zippedFile);
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setStringList("images", _tempImages);
setState(() {
_images = List<String>.from(_tempImages);
_downloading = false;
});
}
unarchiveAndSave(var zippedFile) async {
var bytes = zippedFile.readAsBytesSync();
var archive = ZipDecoder().decodeBytes(bytes);
for (var file in archive) {
var fileName = '$_dir/${file.name}';
print("fileName ${fileName}");
if (file.isFile && !fileName.contains("__MACOSX")) {
var outFile = File(fileName);
//print('File:: ' + outFile.path);
_tempImages.add(outFile.path);
outFile = await outFile.create(recursive: true);
await outFile.writeAsBytes(file.content);
}
}
}
buildList() {
return _images == null
? Container()
: Expanded(
child: ListView.builder(
itemCount: _images.length,
itemBuilder: (BuildContext context, int index) {
return Image.file(
File(_images[index]),
fit: BoxFit.fitWidth,
);
},
),
);
}
progress() {
return Container(
width: 25,
height: 25,
padding: EdgeInsets.fromLTRB(0.0, 20.0, 10.0, 20.0),
child: CircularProgressIndicator(
strokeWidth: 3.0,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
_downloading ? progress() : Container(),
IconButton(
icon: Icon(Icons.file_download),
onPressed: () {
_downloadZip();
},
),
],
),
body: Container(
child: Column(
children: <Widget>[
buildList(),
],
),
),
);
}
}
void main() {
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: DownloadAssetsDemo(),
);
}
}

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