I am trying to get data from an API to populate a Map and then display the screen to show the data from the Map
My getter is as follows:
Map data = {};
Future getData(symbol) async {
final response = await http.get(Uri.parse(
"https://financialmodelingprep.com/api/v3/quote/" +
symbol +
"?apikey=${api_key}"));
if (response.statusCode == 200) {
final jsonResponse = json.decode(response.body);
// print(jsonResponse);
data = jsonResponse[0];
print(data);
}
}
For starters, I am trying to display the symbol and name from the data in the API:
void initState() {
getData(data['symbol']);
super.initState();
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
toolbarHeight: 80,
centerTitle: true,
backgroundColor: Colors.white,
automaticallyImplyLeading: true,
title: Column(
children: [
Text("Symbol", style: TextStyle(color: Colors.black)),
Text("Name", style: TextStyle(color: Colors.black))
],
),
),
);
}
After opening one screen successfully, on opening the next screen, I get the error:
type 'Null' is not a subtype of type 'String'
Can anyone suggest steps to deal with the issue? Any help would be appreciated.
Thanks
So there are a few things that could have gone wrong in the code that you've provided. The getData function is an asynchronous function marked as async and needs to be inside an asynchronous context to actually work properly. What is probably happening in this case is that the data doesn't even have enough time to get downloaded before your widget gets loaded on the screen.
What you need to do is to use FutureBuilder by having a look at the documentation and the introduction video which is in the link I've provided for you.
The FutureBuilder will be able to execute a Future<T> and inside its builder you will be able to retrieve an AsyncSnapshot of either your Future's data or an error.
Related
I'm trying to get a Flutter project to display a simple list of items returned from the Firebase Realtime Database. The code mostly works, but I have to restart the app each time I log out and log back in as a different user, which isn't what I'm looking for. I need the user's data to appear when they log in. I don't quite understand what all is happening here (I stumbled across a functional solution after several days of trial and error and googling), but I thought a Stream was more or less a 'live' stream of data from a particular source.
EDIT: kPAYEES_NODE is a constant stored elsewhere that resolves to 'users/uid/payees' in the RTDB:
import 'package:firebase_database/firebase_database.dart';
import 'auth_service.dart';
final DatabaseReference kUSER_NODE =
FirebaseDatabase.instance.ref('users/${AuthService.getUid()}');
final DatabaseReference kPAYEES_NODE = kUSER_NODE.child('payees');
Here's the code in question:
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(...),
body: StreamBuilder(
stream: kPAYEES_NODE.onValue,
builder: (context, snapshot) {
final payees = <Payee>[];
if (!snapshot.hasData) {
return Center(child: Column(children: const [Text('No Data')]));
} else {
final payeeData =
(snapshot.data!).snapshot.value as Map<Object?, dynamic>;
payeeData.forEach((key, value) {
final dataLast = Map<String, dynamic>.from(value);
final payee = Payee(
id: dataLast['id'],
name: dataLast['name'],
note: dataLast['note'],
);
payees.add(payee);
});
return ListView.builder(
shrinkWrap: true,
itemCount: payees.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text(
payees[index].name,
style: const TextStyle(color: Colors.white),
),
subtitle: Text(
payees[index].id,
style: const TextStyle(color: Colors.white),
),
);
});
}
},
),
floatingActionButton: (...),
);
}
}
That's because you only get the UID of the current once in your code, here:
FirebaseDatabase.instance.ref('users/${AuthService.getUid()}')
And by the time this line runs, the AuthService.getUid() has a single value. Instead, the code needs to be reevaluated any time a users signs in or out of the app.
In an app where the user can sign in and out, the UID values are a stream like the one exposed by FirebaseAuth.instance.authStateChanges() as shown in the Firebase documentation on getting the current user. You can wrap the stream that is returned by authStateChanges() in a StreamBuilder, and then create the database reference inside of that builder, to have it respond to changes in the authentication state.
I'm trying to display an image based on (base64) data coming from a backend, but I keep getting the error bytes != null': is not true.
Here's my code:
class _FuncState extends State<Func> {
Uint8List userIconData;
void initState() {
super.initState();
updateUI();
}
void updateUI() async {
await getUserIconData(1, 2, 3).then((value) => userIconData = value);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
Container(
child: CircleAvatar(
backgroundImage: Image.memory(userIconData).image, // <--- problem here
maxRadius: 20,
),
),
),
);
}
}
Helper code:
Future<Uint8List> getUserIconData(
role,
id,
session,
) async {
var url = Uri.https(kMobileAppAPIURL, kMobileAppAPIFolder);
var response = await http.post(url, body: {
'method': 'getUserProfilePic',
'a': id.toString(),
'b': role.toString(),
'c': session.toString(),
});
if (response.statusCode == 200) {
Map data = jsonDecode(response.body);
return base64Decode(data['img']);
}
return null;
}
I have stepped through the code with a debugger and I have confirmed the helper function is returning the correct series of bytes for the image.
I'd appreciate any pointers.
Further note. The error also says:
Either the assertion indicates an error in the framework itself, or we
should provide substantially more information in this error message to
help you determine and fix the underlying cause. In either case,
please report this assertion by filing a bug on GitHub
This is quite simple; if you take a look at your code you should be able to follow through this sequence of operations.
The widget is created. No action. At this point userIconData is null.
initState is called. async http call is initiated. userIconData == null
build is called. build occurs, throws error. userIconData == null
http call returns. userIconData is set. userIconData == your image
Due to not calling setState, your build function won't run again. If you did, this would happen (but you'd still have had the exception earlier).
build is called. userIconData is set. userIconData == your image
The key here is understanding that asynchronous calls (anything that returns a future and optionally uses async and await) do not return immediately, but rather at some later point, and that you can't rely on them having set what you need in the meantime. If you had previously tried doing this with an image loaded from disk and it worked, that's only because flutter does some tricks that are only possible because loading from disk is synchronous.
Here are two options for how you can write your code instead.
class _FuncState extends State<Func> {
Uint8List? userIconData;
// if you're using any data from the `func` widget, use this instead
// of initState in case the widget changes.
// You could also check the old vs new and if there has been no change
// that would need a reload, not do the reload.
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
updateUI();
}
void updateUI() async {
await getUserIconData(widget.role, widget.id, widget.session).then((value){
// this ensures that a rebuild happens
setState(() => userIconData = value);
});
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
// this only uses your circle avatar if the image is loaded, otherwise
// show a loading indicator.
child: userIconData != null ? CircleAvatar(
backgroundImage: Image.memory(userIconData!).image,
maxRadius: 20,
) : CircularProgressIndicator(),
),
),
);
}
}
Another way to do the same thing is to use a FutureBuilder.
class _FuncState extends State<Func> {
// using late isn't entirely safe, but we can trust
// flutter to always call didUpdateWidget before
// build so this will work.
late Future<Uint8List> userIconDataFuture;
#override
void didUpdateWidget(Func oldWidget) {
super.didUpdateWidget(oldWidget);
userIconDataFuture =
getUserIconData(widget.role, widget.id, widget.session);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
child: FutureBuilder(
future: userIconDataFuture,
builder: (BuildContext context, AsyncSnapshot<Uint8List> snapshot) {
if (snapshot.hasData) {
return CircleAvatar(
backgroundImage: Image.memory(snapshot.data!).image,
maxRadius: 20);
} else {
return CircularProgressIndicator();
}
},
),
),
),
);
}
}
Note that the loading indicator is just one option; I'd actually recommend having a hard-coded default for your avatar (i.e. a grey 'user' image) that gets switched out when the image is loaded.
Note that I've used null-safe code here as that will make this answer have better longevity, but to switch back to non-null-safe code you can just remove the extraneous ?, ! and late in the code.
The error message is pretty clear to me. userIconData is null when you pass it to the Image.memory constructor.
Either use FutureBuilder or a condition to check if userIconData is null before rendering image, and manually show a loading indicator if it is, or something along these lines. Also you'd need to actually set the state to trigger a re-render. I'd go with the former, though.
I am new to flutter and recently I decided to look into providers to be able to reuse some sqlite DB calls I have using FutureBuilder as suggested in this other question.
After reading about it I started using riverpod_hooks but I got stuck.
Currently I have a provider to get the data from sqlite:
final bloodPressureProvider = FutureProvider((_) => _findAllBloodPressures());
Future<List<BloodPressure>> _findAllBloodPressures() async {
var _bloodPressure = BloodPressure.forSearchOnly();
var result = await _bloodPressure.findAll();
return result;
}
Which I can successfully use to render a chart and display a list:
class BloodPressureList extends HookWidget {
#override
Widget build(BuildContext context) {
final bpList = useProvider(bloodPressureProvider);
return Scaffold(
drawer: NutriDrawer(context).drawer(),
appBar: AppBar(
title: Text('Blood Pressure History'),
),
body: Column(
children: [
Container(
padding: EdgeInsets.fromLTRB(0, 25, 15, 5),
child: bpList.when(
data: (bloodPressureList) => _buildLineChart(bloodPressureList),
loading: () => CircularProgressIndicator(),
error: (_, __) => Text('Ooooopsss error'),
),
),
Expanded(
child: bpList.when(
data: (bloodPressureList) => _getSlidableListView(bloodPressureList, context),
loading: () => CircularProgressIndicator(),
error: (_, __) => Text('Ooopsss error'),
),
),
],
), ...
The issue I currently have is to make the notification to the widgets whenever a new element is added to the DB which is done in another screen/widget, before using providers I would use setState but now I understand I should implement possibly a StateNotifier but I am missing the proper way to do it integrated with the asynchronous call returned from the DB used in the provider fragment above.
Any pointers in the right direction is greatly appreciated.
Update with solution: Based on the accepted answer below I used context.refresh and it did exactly what I was looking for:
var result = Navigator.push(context, MaterialPageRoute(builder: (context) {
return BloodPressureDetail(title, bloodPressure);
}));
result.then((_) => context.refresh(bloodPressureProvider));
So when navigating back from the edit/add screen the provider context is refreshed and the state of the chart and list get updated automatically.
By default FutureProvider cache the result of the future so it's only fetched the first time it's accessed.
If you want to get a new value each time you go to the screen use FutureProvider.autoDispose.
If you want to manually refresh the provider you can use context.refresh(bloodPressureProvider) with FutureProvider.
And if you don't want to access you db for fetching values but still want to have screens synchronized you can implement a stateNotifier which will represent your db state
final dbState =
StateNotifierProvider<dbState>((ProviderReference ref) {
final dbState = DbState();
dbState.init();
return dbState;
});
class dbState extends StateNotifier<AsyncValue<List<String>>> {
/// when the constructor is called the initial state is loading
dbState() : super(AsyncLoading<List<String>>());
/// fetch the values from the database and set the state to AsyncData
///(could be good to add a try catch and set the sate to an error if one
/// occurs)
void init()async{
state = AsyncLoading<List<String>>();
final fetchedValues = await fetchValues();
state = AsyncData<List<String>>(fetchedValues);
}
}
I'm trying to display a PDF file in Flutter which I have previously downloaded from a server.
I have tried both flutter_full_pdf_viewer and advance_pdf_viewer. Both libs show me the correct number of pages, but the pages are all white.
Does anybody have an idea why? makes no difference if I run it on iOS or Android or in emulator or real device.
class _PdfPageState extends State<PdfPage> {
String pathPDF = "";
File file;
PDFDocument doc = null;
#override
void initState() {
super.initState();
WeeklyReportsRepository( userRepository: UserRepository()).loadWeeklyReport(widget.weeklyReport.filename).then((file) {
setDoc(file);
});
}
Future<void> setDoc(File file) async {
var doc1 = await PDFDocument.fromFile(file);
setState(() {
doc = doc1;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.white),
title: Text(
"Wochenbericht",
style: TextStyle(
color: Colors.white,
),
),
),
body: Container(
color: Theme
.of(context)
.backgroundColor,
child: (doc == null) ? Center(child: CircularProgressIndicator()) :
PDFViewer(document: doc,
scrollDirection: Axis.vertical,),
),
);
}
}
Everything seems right but can you try to view a pdf from your computer or a link and also try to view specific page of the pdf. Package document try load from assets, URL, or file as shown in the link if these ways work you have a problem from server side.
In my case, it is the file name which was in UTF-8 characters. Once I changed the pdf file name to english letters, advance_pdf_viewer can read it with or without .pdf extension.
Make sure the Pdf File is alphabet file, not a pic converted to a pdf.
I'm writing a Flutter app and I decided to use RxDart to pass my data and events along the managers, services and UI.
Basically I have a service which fetches data from a web service and returns it. Let's assume it returns a List of a model called ExploreEntity.
class ExploreMockService extends ExploreServiceStruct {
final String response = /** a sample json **/;
#override
Future<List<ExploreEntity>> loadExploreData(PaginationInput input) async {
await Future.delayed(new Duration(seconds: 2));
return List<ExploreEntity>.from(jsonDecode(response));
}
}
Now in my manager class I call the loadExploreData method inside a RxCommand.
class ExploreManagerImplementation extends ExploreManager {
#override
RxCommand<void, List<ExploreEntity>> loadExploreDataCommand;
ExploreManagerImplementation() {
loadExploreDataCommand = RxCommand.createAsync<PaginationInput, List<ExploreEntity>>((input) =>
sl //Forget about this part
.get<ExploreServiceStruct>() //and this part if you couldn't understand it
.loadExploreData(input));
}
}
And finally I get the result by a RxLoader and pass it to a GridView if data was fetched successfully.
class ExplorePageState extends State<ExplorePage>{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Explore"),
),
body: Column(children: <Widget>[
Expanded(
child: RxLoader<List<ExploreEntity>>(
commandResults:
sl.get<ExploreManager>().loadExploreDataCommand.results,
dataBuilder: (context, data) => ExploreGridView(data),
placeHolderBuilder: (context) => Center(
child: Center(
child: CircularProgressIndicator(),
),
),
errorBuilder: (context, error) => Center(
child: Text("Error"),
)),
)
]));
}
}
It works like a charm but when I wanted to load the data of the next page from web service and append it to the list, I couldn't find a solution to store the content of previous pages and just append the new page's contents to them, since data is passed along the RxCommand and RxLoader automatically.
When loadExploreData sends the reponse to the manager, I need to firstly append the result to a list, and then send that list as the result to RxLoader. Any suggestions?
Hmm that's a good question if this can be done just using Rx. What I would do is keeping a list of the received items in the manager. So when triggering the command to get the next page the command would first add the new data to the list and then push the whole list to the UI.
I"m curious if there is another solution.
My described approach in a rough code sample
class ExploreManagerImplementation extends ExploreManager {
List<ExploreEntity>> receivedData = <ExploreEntity>[];
#override
RxCommand<void, List<ExploreEntity>> loadExploreDataCommand;
ExploreManagerImplementation() {
loadExploreDataCommand = RxCommand.createAsync<PaginationInput, List<ExploreEntity>>((input)
async {
var newData = await sl //Forget about this part
.get<ExploreServiceStruct>() //and this part if you couldn't understand it
.loadExploreData(input);
receivedData.addAll(newData);
return receivedData;
};
}
}