I am developing an App in flutter specifically for iOS (at this stage) and I need to add PDF file(s) to it. The problem is that flutter has no native way to display PDF files (as far as I researched).
From this tread it looks like it shouldn't it be too difficult to add PDF support to iOS devices using this plugin. However, I am still confused about how exactly to integrate it into my Flutter Application.
Any help would be appreciated!
When I was implementing the functionality for PDF viewer, there was no PDF plugin.
However, funny enough a friend at work found out that there is already a PDF viewer implemented for Flutter here, which we ended up using.
Note: At the time of writing the question, 16.08 there was not yet any plugin available. The mentioned was created on 30.08.
If you are looking for quick and easy way to display PDF, this might be the one.
pdf_flutter
Load PDF from network:
PDF.network(
'https://raw.githubusercontent.com/FlutterInThai/Dart-for-Flutter-Sheet-cheet/master/Dart-for-Flutter-Cheat-Sheet.pdf',
height: 500,
width: 300,
)
Load PDF from files:
File fileName;
PDF.file(
fileName,
height: 200,
width: 100,
)
Load PDF from assets:
PDF.assets(
"assets/pdf/demo.pdf",
height: 200,
width: 100,
)
add the dependencies in the pubspec.yaml
pubspec.yaml
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^0.1.2
pdf_viewer_plugin: ^1.0.0+2
path_provider: ^1.6.1
http: ^0.12.0+4
main.dart
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:pdf_viewer_plugin/pdf_viewer_plugin.dart';
import 'package:path_provider/path_provider.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String path;
#override
initState() {
super.initState();
}
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 {
final response = await http.get(
'https://expoforest.com.br/wp-content/uploads/2017/05/exemplo.pdf');
final responseJson = response.bodyBytes;
return responseJson;
}
loadPdf() async {
writeCounter(await fetchPost());
path = (await _localFile).path;
if (!mounted) return;
setState(() {});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Plugin example app'),
),
body: Center(
child: Column(
children: <Widget>[
if (path != null)
Container(
height: 300.0,
child: PdfViewer(
filePath: path,
),
)
else
Text("Pdf is not Loaded"),
RaisedButton(
child: Text("Load pdf"),
onPressed: loadPdf,
),
],
),
),
),
);
}
}
there working code -
Add this plugin to pubspec.yaml -
>> flutter_full_pdf_viewer: ^1.0.6
on alert dialog positive button click I redirecting to view pdf -
Future<void> onPositiveButtonClick() async {
String _filePath = '';
String _directory = await ExtStorage.getExternalStoragePublicDirectory(ExtStorage.DIRECTORY_DOWNLOADS);
//todo geeting right directory path here
print("directory" + _directory);
_filePath = '$_directory/$fileName';
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => ActivityPdfView(_filePath)));
}
and this is my Activitypdfview to show pdf -
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/flutter_full_pdf_viewer.dart';
class ActivityPdfView extends StatefulWidget {
String filePath;
ActivityPdfView(String filePath){
this.filePath = filePath;
}
#override
_ActivityPdfViewState createState() => _ActivityPdfViewState();
}
class _ActivityPdfViewState extends State<ActivityPdfView> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyPDFList(widget.filePath), //call MyPDF List file
);
}
}
class MyPDFList extends StatelessWidget {
String pathPDF = "";
MyPDFList(String filePath){
this.pathPDF = filePath;
}
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Document"),
backgroundColor: Colors.deepOrangeAccent,
),
path: pathPDF
);
}
}
Depending on what's the min version your app supports, if it's iOS 11 and above, you can use PDFKit. Otherwise WebKit is also a good option.
Related
my question is whether I have to do any additional steps in order to get a list of available cameras on iOS.
Even when following the docs and trying examples from other online resources, I can't seem to get a list of available cameras.
Here's the code. It's super basic but still doesn't give me any cameras.
dependencies:
flutter:
sdk: flutter
camera:
I've placed the dependency in the pubspec.yaml file
Performing hot restart...
Restarted application in 1,146ms.
flutter: []
The output is simply an empty array.
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
void main() => runApp(CameraApp());
class CameraApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: CameraScreen(),
);
}
}
class CameraScreen extends StatefulWidget {
#override
_CameraScreenState createState() => _CameraScreenState();
}
class _CameraScreenState extends State<CameraScreen> {
#override
void initState() {
super.initState();
availableCameras().then((availableCameras) {
print(availableCameras);
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Container(
child: Text('Hello, World'),
),
),
);
}
}
Any ideas on what I'm doing wrong?
Thanks
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:carousel_pro/carousel_pro.dart';
import 'package:http/http.dart' as http;
class Home extends StatelessWidget
{
#override
Widget build(BuildContext context)
{
return MyApp(post: fetchPost());
}
}
Future<Post> fetchPost() async {
final response = await http.get('https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=Zambia');
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
return Post.fromJson(json.decode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
class Post {
final int pageid;
final int ns;
final String title;
final String extract;
Post({this.pageid, this.ns, this.title, this.extract});
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
pageid: json['pageid'],
ns: json['ns'],
title: json['title'],
extract: json['extract'],
);
}
}
class ImageCarousel extends StatelessWidget
{
final carousel = Carousel(
showIndicator: false,
boxFit: BoxFit.cover,
images: [
AssetImage('assets/images/animals.jpg'),
AssetImage('assets/images/bigfalls.jpg'),
AssetImage('assets/images/resort.jpg'),
AssetImage('assets/images/soca.jpg'),
AssetImage('assets/images/towncity.jpg')
],
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(microseconds: 20000),
);
#override
Widget build(BuildContext context)
{
double screenHeight = MediaQuery.of(context).size.height / 3;
return ListView(
children: <Widget>[
Container(
height: screenHeight,
color: Colors.red,
child: carousel,
),
const Text('About Zambia', style: TextStyle(fontWeight: FontWeight.bold)),
],
);
}
}
class MyApp extends StatelessWidget {
final Future<Post> post;
MyApp({Key key, this.post}) : super(key: key);
#override
Widget build(BuildContext context) {
return new Container(
child: FutureBuilder<Post>(
future: post,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner
return CircularProgressIndicator();
},
),
);
}
}
I'm using the example in flutter's documentation on how to fetch data from the internet (https://flutter.io/docs/cookbook/networking/fetch-data), and in place of https://jsonplaceholder.typicode.com/posts/1 I'm using ( https://en.wikipedia.org/w/api.php?format=json&action=query&prop=extracts&exintro&explaintext&redirects=1&titles=Zambia ) but for some reason, I can't get the information to be displayed on my App, forgive the ignorance but I'm new to programming and flutter...
You are parsing the json in the wrong way: the json from that url has a different structure, the keys you are trying to fetch are nested inside "query":{"pages":{"34415" while you are searching for them at the top level.
E.g. in this case, this :
pageid: json['pageid']
should be:
pageid: json['query']['pages']['34415']['pageid']
But it works only in this specific case. Instead, you should first fetch all the pages you get by that query from json['query']['pages'] then loop over the keys (the ids of every page got) and fetch the pages.
I want to users can change and save the theme color in my app. However, I have no ideas how to load the saved theme color when the app starts running. For example, I want to load the saved theme color directly in the comment place below. I tried SharedPreference. However, the SharedPreference instance needs to run with await. It seems can't be used here. Is there any way I can load the saved theme here directly instead of using setState or something like it?
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: // how to load saved theme here?
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
This answer goes a bit further. It shows how to load and save theme preferences, how to build a ThemeData, and how to change the theme from a page of your app.
Save the user preferences (which theme is selected) using the shared_preferences plugin.
Use the "controller pattern" that is used throughout the Flutter framework to provide the currently selected theme (and changes to it) to your app.
Use an InheritedWidget to use the controller in any part of your app.
Here is how the controller looks like:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// provides the currently selected theme, saves changed theme preferences to disk
class ThemeController extends ChangeNotifier {
static const themePrefKey = 'theme';
ThemeController(this._prefs) {
// load theme from preferences on initialization
_currentTheme = _prefs.getString(themePrefKey) ?? 'light';
}
final SharedPreferences _prefs;
String _currentTheme;
/// get the current theme
String get currentTheme => _currentTheme;
void setTheme(String theme) {
_currentTheme = theme;
// notify the app that the theme was changed
notifyListeners();
// store updated theme on disk
_prefs.setString(themePrefKey, theme);
}
/// get the controller from any page of your app
static ThemeController of(BuildContext context) {
final provider = context.inheritFromWidgetOfExactType(ThemeControllerProvider) as ThemeControllerProvider;
return provider.controller;
}
}
/// provides the theme controller to any page of your app
class ThemeControllerProvider extends InheritedWidget {
const ThemeControllerProvider({Key key, this.controller, Widget child}) : super(key: key, child: child);
final ThemeController controller;
#override
bool updateShouldNotify(ThemeControllerProvider old) => controller != old.controller;
}
Here is how you would use the controller and InheritedWidget in your app:
void main() async {
// load the shared preferences from disk before the app is started
final prefs = await SharedPreferences.getInstance();
// create new theme controller, which will get the currently selected from shared preferences
final themeController = ThemeController(prefs);
runApp(MyApp(themeController: themeController));
}
class MyApp extends StatelessWidget {
final ThemeController themeController;
const MyApp({Key key, this.themeController}) : super(key: key);
#override
Widget build(BuildContext context) {
// use AnimatedBuilder to listen to theme changes (listen to ChangeNotifier)
// the app will be rebuilt when the theme changes
return AnimatedBuilder(
animation: themeController,
builder: (context, _) {
// wrap app in inherited widget to provide the ThemeController to all pages
return ThemeControllerProvider(
controller: themeController,
child: MaterialApp(
title: 'Flutter Demo',
theme: _buildCurrentTheme(),
home: MyHomePage(),
),
);
},
);
}
// build the flutter theme from the saved theme string
ThemeData _buildCurrentTheme() {
switch (themeController.currentTheme) {
case "dark":
return ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.orange,
);
case "light":
default:
return ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
);
}
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
// thanks to the inherited widget, we can access the theme controller from any page
ThemeController.of(context).setTheme('light');
},
child: Text('Light Theme'),
),
RaisedButton(
onPressed: () {
ThemeController.of(context).setTheme('dark');
},
child: Text('Dark Theme'),
)
],
),
),
);
}
}
You have a few options as to how you'd load it. The first is as Gunter said in a comment - you make MyApp into a stateful widget and load it with initState(), then setState it.
That would look something like this:
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
ThemeData theme = ThemeData.dark(); // whatever your default is
#override
void initState() {
super.initState();
SharedProperties.getInstance().then((prefs) {
ThemeData theme = ThemeData.light(); // load from prefs here
setState(() => this.theme = theme);
});
}
...
}
The second option is to use a FutureBuilder.
class MyApp extends StatelessWidget {
final Future<ThemeData> loadThemeData = SharedPreferences.getInstance().then((prefs) {
... get theme from prefs
return ThemeData.light();
});
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: loadThemeData,
builder: (context, snapshot) {
return MaterialApp(
theme: snapshot.data,
);
},
initialData: ThemeData.dark(), // whatever you want your default theme to be
);
}
}
The third option is to do the loading before you actually start your app - in your main method. I don't know if this is really recommended as if sharedpreferences takes a while it could delay the start of your app, but realistically it should be very quick and you probably want to avoid a flash different theme showing anyways.
main() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
ThemeData theme = ThemeData.dark(); // get theme from prefs
runApp(MyApp(
theme: theme,
));
}
class MyApp extends StatelessWidget {
final ThemeData theme;
const MyApp({Key key, #required this.theme}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: theme,
....
);
}
}
Load theme data from local storage in main function as await
Okay so I have a relatively simple flutter program set up to test the video_player plugin (https://pub.dartlang.org/documentation/video_player/0.5.1/.)
The player works fine when I use the "Network" or "Asset" constructors for the controller but when I try to use the "file" constructor I run into permission denied errors. The file I am pointing to is an mp4 file that lives in the application documents folder ...
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
void main() => runApp(new MyApp());
//WILL NOT CHANGE
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
//WILL CHANGE
class _MyAppState extends State<MyApp> {
String _title = 'App Bar Demo';
String _myState = 'NO STATE';
VideoPlayerController _controller;
bool _isPlaying = false;
void setControllerDir() async{
}
Future<String> appDir() async{
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
void _pressed(String message) async {
//CORRECT
setState(() {
_myState = message;
}
);
var testPath = await appDir();
print(testPath);
//WRONG
//_myState = message;
print(_myState);
}
#override
void initState() {
super.initState();
//_controller = new VideoPlayerController.network('http://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_20mb.mp4',)
//_controller = new VideoPlayerController.asset('assets/test.mp4')
var file = new File('/data/user/0/com.example.videotest/app_flutter/test.mp4');
//_controller = new VideoPlayerController.asset('assets/test.mp4')
//final directory = await getApplicationDocumentsDirectory();
_controller = new VideoPlayerController.file(file)
..addListener(() {
final bool isPlaying = _controller.value.isPlaying;
if (isPlaying != _isPlaying) {
setState(() {
_isPlaying = isPlaying;
});
}
})
..initialize();
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: _title,
home: new Scaffold(
appBar: new AppBar(
title: new Text(_title),
actions: <Widget>[
new Text('text'),
new IconButton(icon: new Icon(Icons.add_alert), onPressed: () {_pressed('Alert Pressed');}),
new IconButton(icon: new Icon(Icons.print), onPressed: () {_pressed('Print Pressed');}),
new IconButton(icon: new Icon(Icons.people), onPressed: () {_pressed('People Pressed');}),
new RaisedButton(child: new Text('Button'),onPressed: () {_pressed('Button Pressed');}),
],
),
body: new Container(
padding: const EdgeInsets.all(10.0),
child: new AspectRatio(
aspectRatio: 1280 / 720,
child: new VideoPlayer(_controller),
),
),
floatingActionButton: new FloatingActionButton(
onPressed:
_controller.value.isPlaying ? _controller.pause : _controller.play,
child: new Icon(
_controller.value.isPlaying ? Icons.pause : Icons.play_arrow,
),
),
),
);
}
}
I'm new to Android and Flutter development. I tried adding external storage permissions to my manifest but that did not help. Anyway I am running flutter beta 2 and flutter doctor checks out fine. I added the video file using the uploader in the Device File Explorer in Android Studio and I can see the file there. Any clues would be appreciated. Thanks :-D
Okay, let me clarify what is going on here in case anyone stumbles upon this issue and needs some clarification. So I have a simple video_player application example that was not working when I tried to access a video file I had uploaded to the /data/user/0/com.example.videotest/app_flutter directory in my emulator using the device file explorer in Android Studio. I was getting permission denied errors. It turns out that I needed to open a terminal and use the "adb" tool to "push" the video file to that directory in order to get the permissions correct on the file and get the file constructor for the VideoController object to work with the file. But first I had to set the adb deamon to run as root to get the permission to push the file. So it boiled down to ...
adb root
adb push test.mp4 /data/user/0/com.example.videotest/app_flutter/
How do I scrape a web page that generates some of its content with a JavaScript element? It is a local court booking site for my squash club and from what I can see, this is the JavaScript script it calls to retrieve the court bookings:
function load_js(url) {
var e = document.createElement("script");
e.src = url;
e.type = "text/javascript";
if (document.getElementsByTagName("head")[0].lastChild.src == e.src) {
document.getElementsByTagName("head")[0].replaceChild(e, document.getElementsByTagName("head")[0].lastChild);
}
else {
document.getElementsByTagName("head")[0].appendChild(e);
}
}
onload = function () {
setInterval("load_js('/js/bookings_reload.js.php')", 30000);
};
How could I replicate this in Flutter? I can scrape the HTML and parse it to create an HTML document, but since Flutter disallows the use of the dart:html library, I can't create a ScriptElement to duplicate the JavaScript script. Is there an alternative in Flutter?
Here is my Dart function so far:
var httpClient = createHttpClient();
getData() async {
// Send POST request to get authorized cookie
var response = await httpClient.post('http://bookings.squashgym.co.nz/login',
body: {'username': username, 'password': password});
// Send get request with authenticated cookie to get bookings
var courts = await httpClient.get('http://bookings.squashgym.co.nz/booking-sheet',
headers: {'cookie': response.headers['set-cookie']});
Document data = parse(courts.body);
httpClient.close();
}
Thanks!
I agree on using WebView to load the URL needed. Here is an example implementation of WebView using the webview_flutter.
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
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: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: WebView(
initialUrl: "https://flutter.dev/",
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
javascriptMode: JavascriptMode.unrestricted,
),
);
}
}
This is how it will look like:
And when the page is rendered you will be able to get the DOM from the WebView. There is also an available example of getting DOM from WebView. Check it here.