How to integrate native_pdf_view into my app (flutter)? - flutter

I want to show a PDF file with the package native_pdf_view (https://pub.dev/packages/native_pdf_view) in my app.
I tried it like this:
class OStundenplan extends StatelessWidget {
final pdfController = PdfController(
document: PdfDocument.openAsset('assets/stundenplan.pdf'),
);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Offline Stundenplan'),
),
body: Builder(builder: (BuildContext context) {
return PdfView(controller: _pdfController);
}));
}
}
ostundenplan() {
Navigator.push(
context,
MaterialPageRoute(
Widget pdfView() => PdfView(
controller: pdfController,
);
}
later in the app:
RaisedButton.icon(onPressed: ostundenplan, icon: Icon(Icons.signal_wifi_off), label: Text('Offline Stundenplan'),),
but it doesn't work. Can anyone help me?
EDIT:
When I try it like pradyot1996 says, I get this:
Compiler message:
lib/main.dart:315:37: Error: Type 'PDFReader' not found.
class _PDFReaderState extends State<PDFReader> {
^^^^^^^^^
lib/main.dart:315:7: Error: Type argument 'invalid-type' doesn't conform to the bound 'StatefulWidget' of the type variable 'T' on 'State' in the supertype 'State' of class '_PDFReaderState'.
- 'StatefulWidget' is from 'package:flutter/src/widgets/framework.dart' ('/C:/flutter/packages/flutter/lib/src/widgets/framework.dart').
Try changing type arguments so that they conform to the bounds.
class _PDFReaderState extends State<PDFReader> {
^
/C:/flutter/packages/flutter/lib/src/widgets/framework.dart:1029:22: Context: This is the type variable whose bound isn't conformed to.
abstract class State<T extends StatefulWidget> with Diagnosticable {
^
Compiler message:
lib/main.dart:315:37: Error: Type 'PDFReader' not found.
class _PDFReaderState extends State<PDFReader> {
^^^^^^^^^
lib/main.dart:315:7: Error: Type argument 'invalid-type' doesn't conform to the bound 'StatefulWidget' of the type variable 'T' on 'State' in the supertype 'State' of class '_PDFReaderState'.
- 'StatefulWidget' is from 'package:flutter/src/widgets/framework.dart' ('/C:/flutter/packages/flutter/lib/src/widgets/framework.dart').
Try changing type arguments so that they conform to the bounds.
class _PDFReaderState extends State<PDFReader> {
^
/C:/flutter/packages/flutter/lib/src/widgets/framework.dart:1029:22: Context: This is the type variable whose bound isn't conformed to.
abstract class State<T extends StatefulWidget> with Diagnosticable {
^
Target kernel_snapshot failed: Exception: Errors during snapshot creation: null
build failed.
^^^^^^^^^^
Looks like it didn't find the PDFReader.
What can I do?
EDIT 2:
That's the red screen I get:
btw, this is my navigator:
ostundenplan() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PDFReader()));
}
EDIT 3:
class PDFReader extends StatefulWidget {
static const route_name = 'pdf_reader';
#override
_PDFReaderState createState() => _PDFReaderState();
}
class _PDFReaderState extends State<PDFReader> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Offline Stundenplan'),
),
body: FutureBuilder(
future: PDFDocument.fromAsset('assets/stundenplan.pdf'),
builder: (_, pdfData) {},
),
);
}
}

Try this package flutter_plugin_pdf_viewer .
This works perfectly fine for me. I had the PDF file saved in firebase storage and open it through the URL.
PDFDocument doc = await PDFDocument.fromURL(LINK);
PDFViewer(document: doc)
EDIT:
Below class shows the implementation. You can even use a StatelessWidget class if you don't need to refresh the state.
class PDFReader extends StatefulWidget {
static const route_name = 'pdf_reader';
#override
_PDFReaderState createState() => _PDFReaderState();
}
class _PDFReaderState extends State<PDFReader> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Document'),
),
body: FutureBuilder(
future: PDFDocument.fromURL('http://www.africau.edu/images/default/sample.pdf'),
builder: (_, pdfData) {
if (pdfData.connectionState == ConnectionState.waiting) {
return CenterCircularProgressBar();
} else if (pdfData.data == null) {
return CenterText('Not able to open PDF file');
} else {
return PDFViewer(document: pdfData.data);
}
},
),
);
}
}
Now you just need to Navigate to PDFReader widget. If you want to pass the PDF data from one screen to another you can also do that instead of hard coding it in the PDFReader screen.
CenterCircularProgressBar() is a custom widget which shows a Circular
progress bar in the center of the screen till the PDF is loaded
and,
CenterText is a custom widget which shows an error if we don't get the
data back from the PDFDocument.fromURL. Code is given below.
So in this line
PDFDocument.fromURL('http://www.africau.edu/images/default/sample.pdf')
You can use the fromAsset, fromURL and fromFile methods provide by the PDFDocument to show the PDF. PDFViewer is custom class provided by the package which will handle the PDF view.
class CenterCircularProgressBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
}
}
class CenterText extends StatelessWidget {
final String stringValue;
CenterText(this.stringValue);
#override
Widget build(BuildContext context) {
return Center(
child: Text(
stringValue,
),
);
}
}

If you wish to do it with the original library "(https://pub.dev/packages/native_pdf_view)" you can do this:
You need to use a Stateful widget if you want to interact with the PDF. (let say your PDF have more than 1 page)
When you call the PdfView remember to include the document and the controller.
(You were missing the asset path in the constructor of PdfView).
PdfView(
controller: _pdfController,
onDocumentLoaded: (document) {
setState(() {
_allPagesCount = document.pagesCount;
});
},
onPageChanged: (page) {
setState(() {
_actualPageNumber = page;
});
},
),
The above code is an extract from the original library example: https://pub.dev/packages/native_pdf_view/example
So your code should look something like this:
ostundenplan() {
Navigator.push(
context,
MaterialPageRoute(
Widget pdfView() => PdfView(
controller: pdfController,
onDocumentLoaded: (document) {
setState(() {
_allPagesCount = document.pagesCount;
});
},
onPageChanged: (page) {
setState(() {
_actualPageNumber = page;
});
},
),
);
}
adding these variables at the top of your class as well:
int _actualPageNumber = _initialPage, _allPagesCount = 0;
This help you without changing your initial library.

Related

How to acces parameter value from constructor in flutter?

I'm having trouble with accessing the value of a parameter from a constructor in my code. I access to this page from another one where I get the value of the parameter:
final route = MaterialPageRoute(
builder: (context) => AnadirJugadores(idPagina: respuesta['id'],));
Navigator.push(context, route);
This is the code of AnadirJugadores:
class AnadirJugadores extends StatefulWidget {
final String idPagina;
AnadirJugadores({required this.idPagina });
String cogerID() {
return this.idPagina;
}
#override
State<AnadirJugadores> createState() => _AnadirJugadoresState();
}
class _AnadirJugadoresState extends State<AnadirJugadores> {
#override
Widget build(BuildContext context) {
.... more code
ElevatedButton(
child: Text(idPartida), // this is the line of the error
onPressed: () {
final data = ClipboardData(text: '25342756374');
Clipboard.setData(data);
},
),
I'm trying to access the value of idPagina. How could I do that?
Thanks in advance.
When using stateful widgets you need to use widget to access the parameters.
child: Text(widget.idPartida),
In StatefulWidget your constructor exists in upper class (it's not in State class), so to access data in this constructor in your state class you should do this:
ElevatedButton(child: Text(widget.idPartida), ...),

UI is not updated after replacing an item in list when using notifyListeners()

I'm using the Provider package for state management in a Flutter app and I have a list model extending ChangeNotifier.
In the list model there is a method to replace a certain element in the list like this:
class MyListModel extends ChangeNotifier {
List<MyListItem> _myList = [];
void replace(Data data) {
int index = _findById(data.id);
if(index == -1) {
return;
}
_myList[index] = MyListItem(data);
log("After replace: " + _myList.toString());
notifyListeners();
}
void add(MyListItem myItem) {
_myList.add(myItem);
notifyListeners();
}
void remove(MyListItem myItem) {
_myList.remove(myItem);
notifyListeners();
}
}
This is the lis and the list item class where the provider is consumed:
class _MyListView extends StatelessWidget {
final Data _data;
const _SelectUpcomingMealList(this.upcomingMeal);
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, index) {
return MyListItem(_data);
}
);
}
}
class MyListItem extends StatelessWidget {
final Data _data;
MyListItem(this._data);
#override
Widget build(BuildContext context) {
return Consumer<MyListModel>(
builder: (context, myListModel, children) => ListTile(
title: Text(_data.name),
subtitle: Text(_data.description),
trailing: const Icon(Icons.add),
onTap: () => replaceMyItem(myListModel, context),
)
);
}
void replaceMyItem(MyListModel myListModel, BuildContext context) {
myListModel.replace(_data);
Navigator.pop(context);
}
}
For some reason the UI is not updating and the replaced item is not displayed, the old item is visible. The logging shows that the list is properly updated (the index also properly calculated), the replaced element is there, but the UI does not update.
The add() and remove() methods work, in these cases the UI properly reflects the change.
Is there something I'm missing in case of an item being replaced?

Widget not updating flutter

I'm trying to change the variable from another stateful class.
class first extends statefulwidget {
bool text = false;
Widget build(BuildContext context) {
setState((){});
return Container(
child: text ? Text('Hello') : Text('check')
);
}
}
class second extends statefulwidget {
Widget build(BuildContext context) {
return Container(
child: IconButton(
onPressed: () {
first fir = first();
setState((){
fir.test = true;
});
}
)
);
}
}
widget shows only check not showing Hello
This is my code...Ignore spelling mistakes and camelcase
Give me the solutions if you know..
If you are trying to access data on multiple screens, the Provider package could help you. It stores global data accessible from all classes, without the need of creating constructors. It's good for big apps.
Here are some steps to use it (there is also a lot of info online):
Import provider in pubspec.yaml
Create your provider.dart file. For example:
class HeroInfo with ChangeNotifier{
String _hero = 'Ironman'
get hero {
return _hero;
}
set hero (String heroName) {
_hero = heroName;
notifyListeners();
}
}
Wrap your MaterialApp (probably on main.dart) with ChangeNotifierProvider.
return ChangeNotifierProvider(
builder: (context) => HeroInfo(),
child: MaterialApp(...),
);
Use it on your application! Call the provider inside any build method and get data:
#override
Widget build(BuildContext context){
final heroProvider = Provider.of<HeroInfo>(context);
return Column {
children: [
Text(heroProvider.hero)
]
}
}
Or set data:
heroProvider.hero = 'Superman';
try to reference to this answer, create function to set boolean in class1 and pass as parameter to class 2 and execute it :
typedef void MyCallback(int foo);
class MyClass {
void doSomething(int i){
}
MyOtherClass myOtherClass = new MyOtherClass(doSomething);
}
class MyOtherClass {
final MyCallback callback;
MyOtherClass(this.callback);
}

Riverpod: List provider is not rebuilding

Flutter riverpod is not notifying the Consumer on the state change when the StateNotifier's type is List, while the same implementation works just fine for other types.
here, I provided a minimal reproducable example:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ProviderScope(
child: MaterialApp(
home: MyHomePage(),
),
);
}
}
class CounterState extends StateNotifier<List<int>> {
static final provider = StateProvider(
(ref) => CounterState(),
);
int get last {
print('last');
return state.last;
}
int get length {
print('len');
return state.length;
}
// the body of this will be provided below
add(int p) {}
CounterState() : super(<int>[0]);
}
class MyHomePage extends ConsumerWidget {
#override
Widget build(BuildContext context, watch) {
void _incrementCounter() {
final _count = Random.secure().nextInt(100);
context.read(CounterState.provider.notifier).state.add(_count);
}
var count = watch(CounterState.provider.notifier).state.length;
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'You have pushed the button this many times: $count',
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}
as for the add method, I tried implementing it in a lot of ways, but neither works.
here is what I tried:
1: just add it straight away:
add(int p) {
state.add(p);
}
2: I also tried the solution suggested in this answer:
add(int p) {
state = [...state, p];
}
3: I tried to destroy the list entirely, and reassign it:
add(int p) {
final _state = [];
// copy [state] to [_state]
for (var item in state) {
_state.add(item);
}
// empty the state
state = [];
// add the new element
_state.add(p);
// refill [state] from [_state]
for (var item in _state) {
state.add(item);
}
print(state.length); // it continues until here and prints
}
Firstly, you are not creating the correct provider to listen to a StateNotifier. You need to change this:
static final provider = StateProvider(
(ref) => CounterState(),
);
to this:
static final provider = StateNotifierProvider<CounterState, List<int>>(
(ref) => CounterState(),
);
Please refer to the Riverpod documentation about the different types of providers.
Secondly, you are not actually watching for state changes, but you are just getting the state object from the notifier.
Change this line:
var count = watch(CounterState.provider.notifier).state.length;
to this:
final count = watch(CounterState.provider).length;
also, your increment method is not correct for StateNotifier providers. Please change this:
context.read(CounterState.provider.notifier).state.add(_count);
to this:
context.read(CounterState.provider.notifier).add(_count);
It should rebuild now when the state changes. However, you do need an implementation of your add method that actually changes the state object itself. I would suggest the second variant you mentioned, that is in my opinion the nicest way to do this:
add(int p) {
state = [...state, p];
}
#TmKVU explained well, so I'm skipping that part. You can also follow riverpod document.
here is my example of riverPod:
stateNotifierProvider
stateProvider
Your widget
import 'dart:math';
import 'package:stack_overflow/exports.dart';
class CounterState extends StateNotifier<List<int>> {
static final provider = StateNotifierProvider(
(ref) => CounterState(),
);
int get last {
print('last');
return state.last;
}
int get length {
print('len');
return state.length;
}
// the body of this will be provided below
add(int p) {}
CounterState() : super(<int>[0]);
}
class MyHomePageSSSS extends ConsumerWidget {
#override
Widget build(BuildContext context, watch) {
void _incrementCounter() {
final _count = Random.secure().nextInt(100);
context.read(CounterState.provider.notifier).state =
context.read(CounterState.provider.notifier).state..add(_count);
}
final countprovider = watch(CounterState.provider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
'You have pushed the button this many times: ${countprovider.length}',
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
child: Icon(Icons.add),
),
);
}
}

How do I navigate to a page that has a constructor parameter?

I have a flutter stateful page. Here it is:
class TestPage extends StatefulWidget {
static const String id = 'TestPage';
final String testString;
TestPage(this.testString);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(child: Text('Hello ${widget.testString}'))
);
}
}
The page has a constructor that takes in a string as a default value.
final String testString;
From another page, I make a call to that page. I want to open it and give it or pass to it a String value:
Navigator.pushNamed(context, TestPage(myString));
However, it is telling me:
that the argument type 'TestPage' cannot be assigned to the parameter type String.
What am I doing wrong? Is this not the correct way to instantiate this class and make it appear?
Thank you
Try with this,
Navigator.push( context, MaterialPageRoute( builder: (context) => TestPage(testString: 'Hello',), ));