Await message in flutter waiting woocommerce rest api - wordpress

i use woocommerce rest api with flutter to get product variations.
Woocommerce rest api is too slowly to get this variations.
I need to send a message to the user to wait for the process to finish.
How to put this message in the code?
#override
Future<List<ProductVariation>> getProductVariations(Product product,
{String lang = 'en'}) async {
try {
final List<ProductVariation> list = [];
int page = 1;
while (true) {
String endPoint =
"products/${product.id}/variations?per_page=100&page=$page";
if (kAdvanceConfig["isMultiLanguages"]) {
endPoint += "&lang=$lang";
}
var response = await wcApi.getAsync(endPoint);
if (response is Map && isNotBlank(response["message"])) {
throw Exception(response["message"]);
} else {
if (response is List && response.isEmpty) {
/// No more data.
break;
}
for (var item in response) {
if (item['visible']) {
list.add(ProductVariation.fromJson(item));
}
}
/// Fetch next page.
page++;
}
}
return list;
} catch (e) {
//This error exception is about your Rest API is not config correctly so that not return the correct JSON format, please double check the document from this link https://docs.inspireui.com/fluxstore/woocommerce-setup/
rethrow;
}
}
Any help?

Refer to this small example:
class Waitscreen extends StatefulWidget {
Waitscreen ({Key key}) : super(key: key);
#override
_WaitscreenState createState() => _WaitscreenState();
}
class _WaitscreenState extends State<Waitscreen> {
bool _isLoading = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: RaisedButton(
child: Text(_isLoading ? "Loading..." : "Load"),
onPressed: () async {
setState((){_isLoading = !_isLoading;});
// TODO
await Future.delayed(Duration(seconds: 5)); // await getProductVariations...
// TODO
setState((){_isLoading = !_isLoading;});
}
),
),
);
}
}
Then you can do something like, according to your need!

Related

type 'Future<dynamic>' is not a subtype of type 'Widget'. flutter [duplicate]

This question already has answers here:
What is a Future and how do I use it?
(6 answers)
Closed 10 months ago.
The idea
I want to display followers. the page take list of followers user id and then display their username.
Error
when I tried to I get an Error say type 'Future<dynamic>' is not a subtype of type 'Widget'
The issue in this line Text(user["username"]),
Code
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class Following extends StatefulWidget {
final following ;
const Following({Key? key, required this.following}) : super(key: key);
#override
_FollowingState createState() => _FollowingState();
}
class _FollowingState extends State<Following> {
/*attribute*/
var following =[];
bool islouded = false;
var usersData= [];
#override
void initState() {
super.initState();
setState(() {
following = widget.following;
});
getFollowing();
}
void getFollowing() {
for(var user in following){
setState(() {
print(user);
// print(getUser(user));
usersData.add( getUser(user));
});
}
setState(() {
islouded = true;
});
}
getUser(uid)async{
try {
if (uid != null) {
var userSnap = await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.get();
var userData = userSnap.data()!;
// print(userSnap.data()!["username"].toString());
return userData;
}
}catch(e){
showSnackBar(context, e.toString());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !islouded?
const Center(
child: CircularProgressIndicator(),
):following.isNotEmpty?
Column(
children: [
for(var user in usersData)
Text(user["username"]),
],
):Text("No following yet!"),
);
}
}
Tried
I tried use FutureBuilder but I did not how to use it right because it return nothing. I believe I'm using it wrong.
the code as follow:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class Following extends StatefulWidget {
final following ;
const Following({Key? key, required this.following}) : super(key: key);
#override
_FollowingState createState() => _FollowingState();
}
class _FollowingState extends State<Following> {
/*attribute*/
var following =[];
bool islouded = false;
var usersData= [];
#override
void initState() {
super.initState();
setState(() {
following = widget.following;
});
getFollowing();
}
void getFollowing() {
for(var user in following){
setState(() {
print(user);
// print(getUser(user));
usersData.add( getUser(user));
});
}
setState(() {
islouded = true;
});
}
getUser(uid) async{
try {
if (uid != null) {
var userSnap = await FirebaseFirestore.instance
.collection('users')
.doc(uid)
.get();
return userSnap;
// print(userSnap.data()!["username"].toString());
// return userData;
}
}catch(e){
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: !islouded?
const Center(
child: CircularProgressIndicator(),
):following.isNotEmpty?
Column(
children: [
for(var user in usersData)
FutureBuilder(
future: user,
builder: (context, snapshot){
switch(snapshot.connectionState){
case ConnectionState.none:
return Text("No following yet!");
case ConnectionState.active:
return Text("active");
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
print(user);//Instance of 'Future<dynamic>'
print(snapshot);//AsyncSnapshot<Object?>(ConnectionState.done, Instance of '_JsonDocumentSnapshot', null, null)
return Text("username");//i want to display username but getting different error
default:
return Text("No following yet");
}
}
)
// Text(user["username"]),
],
):Text("No following yet!"),
);
}}
Thank you for taking the time reading my question. I hope you have beautiful day like you <3
I feel this may be the culprit:
usersData.add( getUser(user));.
Try this instead: await usersData.add( getUser(user));.
As you call the async method getUser(user) async { ... } it returns a Future, and this Future gets added to the List not the user. This would explain the error complaining about an unexpected Future.

Flutter Firebase create a dynamic list of streams

How i can create a dynamic list of stream?
I need a function that build a stream list and return the stream.
My fuction (try to return a value like this List<Stream<'dynamic'>>:
List<Stream<dynamic>> _buildStreamList() async* {
var chat_overview_object = await query_chat_overview_id();
List<Stream<dynamic>> stream_list = [];
for (var i = 0; i < chat_overview_object.length; i++) {
stream_list.add(
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1234')
.snapshots(),
);
}
yield stream_list;
}
Why i need this?
I try to build a streambuilder with multiple stream on documents.
The streams can be different (for example one stream look at collection a document a other on collection b document b).
The example work but only with fix streams. Now i would like implement a **function which return a dynamic list of streams.
Here my code:
var _data = [];
typedef MultiStreamWidgetBuilder<T> = Widget Function(BuildContext context);
// A widget that basically re-calls its builder whenever any of the streams
// has an event.
class MultiStreamBuilder extends StatefulWidget {
const MultiStreamBuilder({
required this.streams,
required this.builder,
Key? key,
}) : super(key: key);
final List<Stream<dynamic>> streams;
final MultiStreamWidgetBuilder builder;
Widget build(BuildContext context) => builder(context);
#override
State<MultiStreamBuilder> createState() => _MultiStreamBuilderState();
}
class _MultiStreamBuilderState extends State<MultiStreamBuilder> {
final List<StreamSubscription<dynamic>> _subscriptions = [];
#override
void initState() {
super.initState();
_subscribe();
}
#override
void didUpdateWidget(MultiStreamBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.streams != widget.streams) {
// Unsubscribe from all the removed streams and subscribe to all the added ones.
// Just unsubscribe all and then resubscribe. In theory we could only
// unsubscribe from the removed streams and subscribe from the added streams
// but then we'd have to keep the set of streams we're subscribed to too.
// This should happen infrequently enough that I don't think it matters.
_unsubscribe();
_subscribe();
}
}
#override
Widget build(BuildContext context) => widget.build(context);
#override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
for (final s in widget.streams) {
final subscription = s.listen(
(dynamic data) {
setState(() {
_data.add(data);
print('data: ' + _data.toString());
});
},
onError: (Object error, StackTrace stackTrace) {
setState(() {});
},
onDone: () {
setState(() {});
},
);
_subscriptions.add(subscription);
}
}
void _unsubscribe() {
for (final s in _subscriptions) {
s.cancel();
}
_subscriptions.clear();
}
}
class AppWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiStreamBuilder(
streams: _buildStreamList(),
[
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1233')
.snapshots(),
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1234')
.snapshots(),
FirebaseFirestore.instance
.collection('chat')
.doc('chat_overview_data')
.collection('data')
.doc('chat_overview_id_1232')
.snapshots()
],
builder: _buildMain,
);
}
}
Widget _buildMain(BuildContext context) {
return MaterialApp(
home: Row(
children: [
Text(
'_data: ' + _data.toString(),
style: TextStyle(fontSize: 15),
)
],
));
}
Edit!
After a while i got a solution:
//multi stream widget builder
typedef MultiStreamWidgetBuilder<T> = Widget Function(BuildContext context);
// widget that basically re-calls its builder whenever any of the streams has an event.
class MultiStreamBuilder extends StatefulWidget {
const MultiStreamBuilder({
required this.streams,
required this.builder,
Key? key,
}) : super(key: key);
final List<Stream<dynamic>> streams;
final MultiStreamWidgetBuilder builder;
Widget build(BuildContext context) => builder(context);
#override
State<MultiStreamBuilder> createState() => _MultiStreamBuilderState();
}
//multi streambuilder
class _MultiStreamBuilderState extends State<MultiStreamBuilder> {
final List<StreamSubscription<dynamic>> _subscriptions = [];
#override
void initState() {
super.initState();
_subscribe();
}
#override
void didUpdateWidget(MultiStreamBuilder oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.streams != widget.streams) {
// Unsubscribe from all the removed streams and subscribe to all the added ones.
// Just unsubscribe all and then resubscribe. In theory we could only
// unsubscribe from the removed streams and subscribe from the added streams
// but then we'd have to keep the set of streams we're subscribed to too.
// This should happen infrequently enough that I don't think it matters.
_unsubscribe();
_subscribe();
}
}
#override
Widget build(BuildContext context) => widget.build(context);
#override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
for (final s in widget.streams) {
final subscription = s.listen(
(dynamic data) {
setState(() {
//check object contain id
var object = chat_overview_object.singleWhere(
(element) => element.chat_overview_id[0].trim() == data.id);
//add to object data
object.last_message_send = data['last_message_send'];
object.last_update = data['last_update'];
object.members_id = data['members_id'];
object.new_message = data['new_message'];
object.user_display_name = data['user_display_name'];
});
//set index
index++;
//check all data load
if (index == chat_overview_object.length) {
//set data has load
data_load.value = false;
}
},
onError: (Object error, StackTrace stackTrace) {
setState(() {});
},
onDone: () {
setState(() {});
},
);
_subscriptions.add(subscription);
}
}
void _unsubscribe() {
for (final s in _subscriptions) {
s.cancel();
}
_subscriptions.clear();
}
}
You are using a wrong approach: You should not build a separate stream for each document, then decide on which stream you are using. Instead, you should build only 1 stream that returns all those documents you may need.
Then, inside your StreamBuilder, you can apply your if condition to decide which document you need to use. This way, the document will always be up-to-date.
Make sure you apply the right where clause to call the appropriate documents from the database.
EDIT 1:
So what you need is known as a collectionGroup:
db.collectionGroup('data').where('yourField', isEqualTo: 'whatever').snapshots()

How can I have this Flutter Bloc to send updates?

I have this Flutter bloc that takes a Firebase stream of restaurants and depending on the position relative to the user will filter only the closest ones depending on the restaurant location. It works fine but I have to refresh with a RefreshIndicator if I want to see any changes in restaurant documents. What am I missing? Thanks in advance.
class NearestRestaurant {
final String id;
final Restaurant restaurant;
final double distance;
NearestRestaurant({this.id, this.restaurant, this.distance});
}
class NearRestaurantBloc {
final Future<List<Restaurant>> source;
final Position userCoordinates;
final _stream = StreamController<List<Restaurant>>();
NearRestaurantBloc({
this.source,
this.userCoordinates,
}) {
List<Restaurant> resList = List<Restaurant>();
source.then((rest) {
rest.forEach((res) async {
await Geolocator().distanceBetween(
userCoordinates.latitude,
userCoordinates.longitude,
res.coordinates.latitude,
res.coordinates.longitude,
).then((distance) {
if (res.active && distance < res.deliveryRadius) {
resList.add(res);
}
});
_stream.add(resList);
});
});
}
Stream<List<Restaurant>> get stream => _stream.stream;
void dispose() {
_stream.close();
}
}
class RestaurantQuery extends StatefulWidget {
#override
_RestaurantQueryState createState() => _RestaurantQueryState();
}
class _RestaurantQueryState extends State<RestaurantQuery> {
NearRestaurantBloc bloc;
#override
Widget build(BuildContext context) {
final database = Provider.of<Database>(context, listen: true);
final session = Provider.of<Session>(context);
final userCoordinates = session.position;
bloc = NearRestaurantBloc(
source: database.patronRestaurants(),
userCoordinates: userCoordinates,
);
return StreamBuilder<List<Restaurant>>(
stream: bloc.stream,
builder: (context, snapshot) {
bool stillLoading = true;
var restaurantList = List<Restaurant>();
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData && snapshot.data.length > 0) {
restaurantList = snapshot.data;
}
stillLoading = false;
}
return Scaffold(
appBar: AppBar(
title: Text(
'Restaurants near you',
style: TextStyle(color: Theme.of(context).appBarTheme.color),
),
elevation: 2.0,
),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
body: RefreshIndicator(
onRefresh: () async {
setState(() {
});
},
child: RestaurantList(
nearbyRestaurantsList: restaurantList,
stillLoading: stillLoading,
),
),
);
},
);
}
#override
void dispose() {
bloc.dispose();
super.dispose();
}
}
In the build method under _RestaurantQueryState, you are returning the scaffold outside the builder method. Initially, restaurantList is null. Therefore, you don't produce the list. Whenever the stream updates, you get the snapshot data to update the restaurantList.
The problem occurs here. Even though the restaurantList is updated, the widget RestaurantList is not updated because it is outside the builder method. You can use the following code. Here we create a Widget that holds the RestaurantList widget. The widget gets updated whenever the stream updates.
class _RestaurantQueryState extends State<RestaurantQuery> {
NearRestaurantBloc bloc;
#override
Widget build(BuildContext context) {
final database = Provider.of<Database>(context, listen: true);
final session = Provider.of<Session>(context);
final userCoordinates = session.position;
//////////////////////////////////
//initialize RestaurantList widget
//////////////////////////////////
Widget restaurantWidget = RestaurantList(
nearbyRestaurantsList: [],
stillLoading: false,
);
bloc = NearRestaurantBloc(
source: database.patronRestaurants(),
userCoordinates: userCoordinates,
);
return StreamBuilder<List<Restaurant>>(
stream: bloc.stream,
builder: (context, snapshot) {
bool stillLoading = true;
var restaurantList = List<Restaurant>();
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData && snapshot.data.length > 0) {
restaurantList = snapshot.data;
/////////////////////////////
//update the restaurant widget
//////////////////////////////
restaurantWidget = RestaurantList(
nearbyRestaurantsList: restaurantList,
stillLoading: stillLoading,
);
}
stillLoading = false;
}
return Scaffold(
appBar: AppBar(
title: Text(
'Restaurants near you',
style: TextStyle(color: Theme.of(context).appBarTheme.color),
),
elevation: 2.0,
),
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
///////////////////////////
//use the restaurant Widget
///////////////////////////
body: restaurantWidget,
),
);
},
);
}
My mistake. I was listening to a future instead of a stream. Here's the updated bloc code:
class NearestRestaurant {
final String id;
final Restaurant restaurant;
final double distance;
NearestRestaurant({this.id, this.restaurant, this.distance});
}
class NearRestaurantBloc {
final Stream<List<Restaurant>> source;
final Position userCoordinates;
final _stream = StreamController<List<Restaurant>>();
NearRestaurantBloc({
this.source,
this.userCoordinates,
}) {
List<Restaurant> resList = List<Restaurant>();
source.forEach((rest) {
resList.clear();
rest.forEach((res) async {
await Geolocator().distanceBetween(
userCoordinates.latitude,
userCoordinates.longitude,
res.coordinates.latitude,
res.coordinates.longitude,
).then((distance) {
if (res.active && distance < res.deliveryRadius) {
resList.add(res);
}
});
_stream.add(resList);
});
});
}
Stream<List<Restaurant>> get stream => _stream.stream;
}

How can I handle a simple In App Subscription with Flutter?

I'm using the in_app_purchase package and I want to handle a subscription in Flutter.
There is only 1 subscription option and it follows a very common pattern...
I have a section that checks if a Users firebase account 'premium' field is true. If so, it displays the section, if false it displays a 'Subscribe Now' Button.
Currently, on Android, the button doesn't show. On iOS, the button shows but when I click Subscribe - it outputs the PurchaseParams correctly but I'm never prompted for payment. I am testing on a physical device with a sandbox account.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:wt_flutter/components/trips/blurred_category.dart';
import 'package:wt_flutter/services/globals.dart';
import 'package:wt_flutter/services/models.dart';
import 'package:wt_flutter/shared/loader.dart';
import 'package:wt_flutter/components/trips/initial_airport.dart';
final String proID = 'go_pro_annual';
class TripsScreen extends StatefulWidget {
const TripsScreen({Key key}) : super(key: key);
#override
_TripsScreenState createState() => _TripsScreenState();
}
class _TripsScreenState extends State<TripsScreen> {
// IAP Plugin Interface
InAppPurchaseConnection _iap = InAppPurchaseConnection.instance;
// Is the API available on the device
bool _available = true;
// Subscriptions for sale
List<ProductDetails> _products = [];
// Past purchases
List<PurchaseDetails> _purchases = [];
// Updates to purchases
StreamSubscription _streamSubscription;
#override
void initState() {
_initialize();
super.initState();
}
#override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}
void _initialize() async {
// Check availablility of In App Purchases
_available = await _iap.isAvailable();
if (_available) {
await _getProducts();
await _getPastPurchases();
// List<Future futures = [_getProducts(), _getPastPurchases()];
// await Future.wait(futures);
_verifyPurchase();
// Listen to new purchases
_streamSubscription =
_iap.purchaseUpdatedStream.listen((data) => setState(() {
print('NEW PURCHASE');
_purchases.addAll(data);
_verifyPurchase();
}));
}
}
// Get all products available for sale
Future<void> _getProducts() async {
Set<String> ids = Set.from([proID]);
ProductDetailsResponse response = await _iap.queryProductDetails(ids);
setState(() {
_products = response.productDetails;
});
}
// Gets past purchases
Future<void> _getPastPurchases() async {
QueryPurchaseDetailsResponse response = await _iap.queryPastPurchases();
for (PurchaseDetails purchase in response.pastPurchases) {
if (Platform.isIOS) {
_iap.completePurchase(purchase);
}
}
// Or for consumables
// TODO query the database for state of consumable products
setState(() {
_purchases = response.pastPurchases;
});
}
// Returns purchase of specific product ID
PurchaseDetails _hasPurchased(String productID) {
return _purchases.firstWhere((purchase) => purchase.purchaseID == productID,
orElse: () => null);
}
// Your own business logic to setup a consumable
void _verifyPurchase() {
PurchaseDetails purchase = _hasPurchased(proID);
//TODO serverside verification & record subscription in the database
if (purchase != null && purchase.status == PurchaseStatus.purchased) {
print('Purchase verified');
}
}
/// Purchase a product
void _buyProduct(ProductDetails prod) {
print(prod);
try {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: prod);
// For one time purchase
print(purchaseParam.productDetails.id);
print(purchaseParam.productDetails.price);
print(purchaseParam.productDetails.title);
_iap.buyNonConsumable(purchaseParam: purchaseParam);
print('purchase successful');
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
User user = Provider.of<User>(context);
if (user.homeAirport != '') {
return Scaffold(
body: FutureBuilder(
future: Global.tripsRef.getData(),
builder: (BuildContext context, AsyncSnapshot snap) {
if (snap.hasData) {
return Center(
child: Container(
color: Colors.white,
child: ListView(
physics: ClampingScrollPhysics(),
scrollDirection: Axis.vertical,
children: <Widget>[
Text('Wavetrotter Suggestions'),
TripList(),
for (var prod in _products)
_available && !user.premium
? FlatButton(
child: Text('Subscribe'),
onPressed: () => _buyProduct(prod),
color: Colors.green,
)
: SizedBox(height: 0.0),
Text('Trips For You'),
!user.premium ? BlurredCategory() : TripList(),
Text('Biggest Swells'),
!user.premium ? BlurredCategory() : TripList(),
Text('No Wetsuit'),
!user.premium ? BlurredCategory() : TripList(),
],
),
),
);
} else {
return LoadingScreen();
}
},
),
);
} else {
return InitialAirport();
}
}
}

Simple Flutter sqflite login->write to db->navigate->retrieve from db flow

What is the correct way to handle this, I have done a lot of searching and most samples which use future builders use them to draw lists so maybe I should be avoiding them all together here.
I want to submit a login form, perform the network request and draw a progress bar while the login is happening, and if successful navigate to a home page. If unsuccessful it should just kill the progress bar and redraw the home page. That part seems to be working, unsure if I am using the Navigator correctly.
The login call returns a user and access token object. The Homepage needs to retrieve the access token which was written to the db by the successful login response. From what I can tell the navigation is happening too quickly and the retrieval of the access token appears to happen before the navigation to the home page.
class LoginPage extends StatefulWidget {
LoginPage({Key key, this.title}) : super(key: key);
final String title;
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
bool _isValidForm = true;
Future<LoginResponse> _user;
void _submitLogin() {
setState(() {
if (_isValidForm) {
_user = login().then((_) => Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage())));
}
});
}
Widget _buildLoginForm(AsyncSnapshot<LoginResponse> snapshot) {
if (snapshot.connectionState != ConnectionState.none && !snapshot.hasData) {
return new Center(child: new CircularProgressIndicator());
} else {
return SafeArea(
child: Center(
child: new ListView(
children: <Widget>[
//..more views
Padding(
padding: EdgeInsets.fromLTRB(16.0, 0.0, 16.0, 16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//..email and password fields
FlatButton(
child: new Text(
'SIGN IN',
),
onPressed: _submitLogin),
]),
)
],
),
),
);
}
}
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future: _user,
builder: (context, AsyncSnapshot<LoginResponse> snapshot) {
return new Scaffold(
backgroundColor: kMyGreen,
body: _buildLoginForm(snapshot),
);
},
);
}
Future<LoginResponse> login() async {
final response = await http.post(...);
if (response.statusCode == 200) {
var loginResponse = LoginResponse.fromJson(json.decode(response.body));
//Write the user details to local db
DBProvider.db.newUser(loginResponse.user);
//Write the tokens to local db
DBProvider.db.newToken(loginResponse.tokens);
return loginResponse;
} else {
throw Exception('Failed to login');
}
}
}
Database methods:
newUser(User newUser) async {
final db = await database;
//get the biggest id in the table
var table = await db.rawQuery("SELECT MAX(id)+1 as id FROM User");
int id = table.first["id"];
//insert to the table using the new id
var raw = await db.rawInsert(
"INSERT Into User (id,first_name,last_name)"
" VALUES (?,?,?)",
[id, newUser.firstName, newUser.lastName]);
return raw;
}
newToken(Tokens newTokens) async {
final db = await database;
//await db.rawDelete("DELETE FROM Token");
//get the biggest id in the table
var table = await db.rawQuery("SELECT MAX(id)+1 as id FROM Token");
int id = table.first["id"];
//insert to the table using the new id
var raw = await db.rawInsert(
"INSERT Into Token (id,access_token,refresh_token)"
" VALUES (?,?,?)",
[id, newTokens.accessToken, newTokens.refreshToken]);
return raw;
}
Future<Tokens> getToken() async {
final db = await database;
var res = await db.query("Token", limit: 1);
return res.isNotEmpty ? Tokens.fromJson(res.first) : null;
}
Home page
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>{
#override
void initState() {
super.initState();
getHomePageStuff();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
),
body: Center(
child: RaisedButton(
onPressed: () {},
child: Text('Go back!'),
),
),
);
}
}
Future<HomePageStuffResponse> getHomePageStuff() async {
Tokens token = await DBProvider.db.getToken();
//Accessing the token here throws an NPE
var accessToken = token.accessToken;
debugPrint("token = " + accessToken);
final response = await http.get(..);
if (response.statusCode == 200) {
debugPrint("FETCH SUCCESS");
return stuff;
} else {
throw Exception('Failed to fetch home page stuff');
}
}
You can simply wrap Scaffold's body in FutureBuilder like this
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
),
body: FutureBuilder<HomePageStuffResponse>(
future: getHomePageStuff(),
builder: (context, snap) {
if(snap.hasError) {
return ErrorWidget('Error occurred while fetching data');
}
if(snap.hasData) {
return Center(
child: RaisedButton(
onPressed: () {},
child: Text('Go back!'),
),
);
}
}
),
);
}
}
Future<HomePageStuffResponse> getHomePageStuff() async {
Tokens token = await DBProvider.db.getToken();
//Accessing the token here throws an NPE
var accessToken = token.accessToken;
debugPrint("token = " + accessToken);
final response = await http.get(..);
if (response.statusCode == 200) {
debugPrint("FETCH SUCCESS");
return stuff;
} else {
throw Exception('Failed to fetch home page stuff');
}
}
Okay I was pretty close. Navigation is fine the way it is, the issue was the writing to the db was not being awaited on so that would happen simultaneously to the navigation (the newUser and newToken calls). As I would navigate to the home screen and try and read the access token the call would fail because it did not exist yet.
This was made a little harder to figure out because the debugger is a little strange in Android Studio for flutter so I just had to log everything to the console to see the issue.
If you read my question thank you for your time :)

Resources