i am using the following function to retrive the UID:
FirebaseAuth auth = FirebaseAuth.instance;
getUID() async {
final FirebaseUser user = await auth.currentUser();
final uid = user.uid;
return uid;
}
After that, i would like to use the UID to acces the right database-doc:
return new StreamBuilder(
stream: Firestore.instance.collection('users').document(getUID()).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
var userDocument = snapshot.data;
But i get the error
Future<dynamic> is not a subtype of type "string"
What is the right way to do it?
FULL CODE
class Settings extends StatelessWidget{
FirebaseAuth auth = FirebaseAuth.instance;
getUID() async {
final FirebaseUser user = await auth.currentUser();
final uid = user.uid;
return uid;
}
static GlobalKey<FormState> _formKey = new GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Indstillinger"),
),
body: _buildSetting(context),
);
}
Widget _buildSetting(BuildContext context) {
return new StreamBuilder(
stream: Firestore.instance.collection('users').document(getUID()).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
var userDocument = snapshot.data;
You need to use stateful widget, like
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class Settings extends StatefulWidget {
#override
_SettingsState createState() {
return _SettingsState();
}
}
class _SettingsState extends State<Settings> {
FirebaseUser user;
String error;
void setUser(FirebaseUser user) {
setState(() {
this.user = user;
this.error = null;
});
}
void setError(e) {
setState(() {
this.user = null;
this.error = e.toString();
});
}
#override
void initState() {
super.initState();
FirebaseAuth.instance.currentUser().then(setUser).catchError(setError);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Indstillinger"),
),
body: user != null ? _buildSetting(context) : Text("Error: $error"),
);
}
Widget _buildSetting(BuildContext context) {
return new StreamBuilder(
stream: Firestore.instance.collection('users').document(user.uid).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
var userDocument = snapshot.data;
// User userDocument here ..........
return new Text(userDocument.toString());
});
}
}
You're passing the function without actually awaiting for it to return. You could store the returned value in a variable before calling StreamBuilder.
final documentId = await getUID();
return new StreamBuilder(
stream: Firestore.instance.collection('users').document(documentId).snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return new Text("Loading");
}
var userDocument = snapshot.data;
Related
I am invoking a method from native Swift code by using the platform channel like this:
channel.invokeMethod(METHOD_NAME, arguments: STRING_ARGUMENT)
in my Flutter class I handle the respective method call by using a callback
platform.setMethodCallHandler(_receiveFromHost);
The setMethodCallHandler() requires the callback to return a Future and I set state in _receiveFromHost method in Dart.
Problem is that my UI is not updated when Swift invokes the callback. Why?
call.method catch method string, state is set, but UI don't update.
video
This is my code in Swift:
let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
let flutterViewController = FlutterViewController(engine: flutterEngine!, nibName: nil, bundle: nil)
let channel = FlutterMethodChannel(name: "flutter_apple_pay", binaryMessenger: flutterViewController.binaryMessenger)
channel.invokeMethod("sendDidFinishAdding", arguments: "payment success")
And this is my code in Flutter:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:convert';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: const Text("Native Code from Dart"),
),
body: new MyHomePage(title: ""),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _state = "Waiting results...";
static const platform = const MethodChannel("flutter_apple_pay");
_MyHomePageState() {
platform.setMethodCallHandler(_receiveFromHost);
}
// #override
// void initState() {
// platform.setMethodCallHandler(_receiveFromHost);
// super.initState();
// }
Future<dynamic> _receiveFromHost(MethodCall call) async {
List certificatesBase64Encoded;
String nonceBase64Encoded;
String nonceSignatureBase64Encoded;
String state;
try {
print(call.method);
if (call.method == "sendGenerateRequestWithData") {
final String data = call.arguments;
final jData = jsonDecode(data);
certificatesBase64Encoded = jData['certificatesBase64Encoded'];
nonceBase64Encoded = jData['nonceBase64Encoded'];
nonceSignatureBase64Encoded = jData['nonceSignatureBase64Encoded'];
print(certificatesBase64Encoded);
print(nonceBase64Encoded);
print(nonceSignatureBase64Encoded);
state = "sendGenerateRequestWithData";
platform.invokeMethod("SendTokenizationData", "TokenizationDataString");
} else if (call.method == "sendDidFinishAdding") {
final String result = call.arguments;
print(result);
state = result;
}
} on PlatformException catch (e) {
print(e);
}
setState(() {
_state = state;
});
print(_state);
}
Future<void> _checkCardState() async {
String cardState;
const primaryAccountIdentifiers = ["a", "b"];
try {
final String result = await platform.invokeMethod(
"checkCardState", primaryAccountIdentifiers);
cardState = result;
} on PlatformException catch (e) {
cardState = "Failed to Invoke: '${e.message}'.";
}
setState(() {
_state = cardState;
});
}
Future<void> _startApplePay() async {
const _cardNetwork = "cn/AMEX";
const _cardHolderName = "pero peric";
const _primaryAccountIdentifier = "a";
const _primaryAccountSuffix = "1234";
const _localizedDescription = "AMEX";
Map<String, dynamic> resultMap = Map();
resultMap['cardNetwork'] = _cardNetwork;
resultMap['cardHolderName'] = _cardHolderName;
resultMap['primaryAccountIdentifier'] = _primaryAccountIdentifier;
resultMap['primaryAccountSuffix'] = _primaryAccountSuffix;
resultMap['localizedDescription'] = _localizedDescription;
platform.invokeMethod("startApplePay", resultMap);
}
#override
Widget build(BuildContext context) {
return Material(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
child: Text('Check Card State'),
onPressed: _checkCardState,
),
ElevatedButton(
child: Text('Start Apple Pay'),
onPressed: _startApplePay,
),
Text(_state),
],
),
),
);
}
}
Thanks!
How to display one by one data using this DB function?
Future<List<Data>> display() async {
//final Database db = await database;
var db = await db1;
final List<Map<String, dynamic>> maps = await db.query('syncTable');
return List.generate(maps.length, (i) {
return Data(
syn_TableName: maps[i]['syn_TableName'],
syn_ChangeSequence: maps[i]['syn_ChangeSequence'],
);
});
}
You can use the FutureBuilder to consume your display() method. Then inside the FutureBuilder you can use AsyncSnapshot.data to get your List of Dataelements.
In the next step you use can call List.map() to map your Data to widgets. In this example I use the ListTile to display:
snapshot.data.map((data) {
return ListTile(
title: Text(data.syn_TableName),
subtitle: Text(data.syn_ChangeSequence),
);
}).toList(),
Here a minimal working example which you can try out:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<List<Data>>(
initialData: [],
future: display(),
builder: (context, snapshot) {
return ListView(
children: snapshot.data.map((data) {
return ListTile(
title: Text(data.syn_TableName),
subtitle: Text(data.syn_ChangeSequence),
);
}).toList(),
);
}),
),
);
}
Future<List<Data>> display() async {
return List.generate(15, (i) {
return Data(
syn_TableName: 'syn_TableName $i',
syn_ChangeSequence: 'syn_ChangeSequence $i',
);
});
}
}
class Data {
final String syn_TableName;
final String syn_ChangeSequence;
Data({this.syn_ChangeSequence, this.syn_TableName});
}
I am developing a simple todo app using flutter with BloC pattern.
It has a ui to display TodoDetails.
When a user click a button, it show a new SimpleDialog.
I want to show some Tag list in the SimpleDialog like:
class AddEditTodoPage extends StatefulWidget {
final TodoRepository todoRepository;
final TagRepository tagRepository;
final Todo todo;
final SaveTodoBloc bloc;
AddEditTodoPage({this.todoRepository, this.tagRepository, this.todo})
: bloc = SaveTodoBloc(
todoRepository: todoRepository,
tagRepository: tagRepository,
todo: todo);
#override
State<StatefulWidget> createState() => _AddEditTodoPageState(todo: todo);
}
class _AddEditTodoPageState extends State<AddEditTodoPage> {
final Todo todo;
_AddEditTodoPageState({this.todo});
#override
Widget build(BuildContext context) {
return Center(
child: StreamBuilder<Tag>(
stream: widget.bloc.tag,
builder: (context, snapshot) {
final tag = snapshot.data;
return OutlineButton(
onPressed: () async {
final selectedTag = await showDialog<TagSelection>(
context: context,
builder: (context) => _showTagSelectDialog(context),
);
},
);
}},
);
}
_showTagSelectDialog(BuildContext context) => SimpleDialog(
title: Text("Select a Tag or create a new one"),
children: <Widget>[
StreamBuilder<List<Tag>>(
stream: widget.bloc.tags,
builder: (context, snapshot) {
final tagList = snapshot.data;
if (tagList == null || tagList.isEmpty) {
// This is always 'null'!!!
return SizedBox();
} else {
return ListView(
children: tagList.map(_buildTagName).toList(),
);
}
}),
],
);
Widget _buildTagName(Tag tag) => Text(tag.name);
}
So my bloc is getting the TagList like:
class SaveTodoBloc {
final TodoRepository todoRepository;
final TagRepository tagRepository;
final Todo todo;
SaveTodoBloc({this.todoRepository, this.tagRepository, this.todo}) {
if (tagRepository != null) {
_getTags();
}
}
final _getTagsSubject = PublishSubject<List<Tag>>();
Stream<List<Tag>> get tags => _getTagsSubject.stream;
Future<Null> _getTags() async {
await tagRepository.getAll().then((list) {
_getTagsSubject.add(list);
print("[SaveTodoBloc][JOS] _getTags - $list"); // It resturns correct list of Tags.
});
}
}
When I check the log, the bloc logic returns correct list of Tags.
But when I show the Dialog, it doesn't have list of tags.
The list is null.
I've been trying without success to load different pages according to my Shared Preference settings.
Based on several posts found in stackoverflow, i end up with the following solution:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:testing/screens/login.dart';
import 'package:testing/screens/home.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Widget page = Login();
Future getSharedPrefs() async {
String user = Preferences.local.getString('user');
if (user != null) {
print(user);
this.page = Home();
}
}
#override
void initState() {
super.initState();
this.getSharedPrefs();
}
#override
Widget build(BuildContext context) {
return MaterialApp(home: this.page);
}
}
class Preferences {
static SharedPreferences local;
/// Initializes the Shared Preferences and sets the info towards a global variable
static Future init() async {
local = await SharedPreferences.getInstance();
}
}
The variable user is not null because the print(user) returns a value as expected, but the login screen is always being opened.
Your problem is that your build method returns before your getSharedPrefs future is complete. The getSharedPrefs returns instantly as soon as it's called because it's async and you're treating it as a "Fire and Forget" by not awaiting. Seeing that you can't await in your initState function that makes sense.
This is where you want to use the FutureBuilder widget. Create a Future that returns a boolean (or enum if you want more states) and use a future builder as your home child to return the correct widget.
Create your future
Future<bool> showLoginPage() async {
var sharedPreferences = await SharedPreferences.getInstance();
// sharedPreferences.setString('user', 'hasuser');
String user = sharedPreferences.getString('user');
return user == null;
}
When user is null this will return true. Use this future in a Future builder to listen to the value changes and respond accordingly.
#override
Widget build(BuildContext context) {
return MaterialApp(home: FutureBuilder<bool>(
future: showLoginPage(),
builder: (buildContext, snapshot) {
if(snapshot.hasData) {
if(snapshot.data){
// Return your login here
return Container(color: Colors.blue);
}
// Return your home here
return Container(color: Colors.red);
} else {
// Return loading screen while reading preferences
return Center(child: CircularProgressIndicator());
}
},
));
}
I ran this code and it works fine. You should see a blue screen when login is required and a red screen when there's a user present. Uncomment the line in showLoginPage to test.
There is a much pretty way of doing this.
Assuming that you have some routes and a boolean SharedPreference key called initialized.
You need to use the WidgetsFlutterBinding.ensureInitialized() function before calling runApp() method.
void main() async {
var mapp;
var routes = <String, WidgetBuilder>{
'/initialize': (BuildContext context) => Initialize(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
};
print("Initializing.");
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesClass.restore("initialized").then((value) {
if (value) {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Home(),
);
} else {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Initialize(),
);
}
});
print("Done.");
runApp(mapp);
}
The SharedPreference Class Code :
class SharedPreferencesClass {
static Future restore(String key) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
return (sharedPrefs.get(key) ?? false);
}
static save(String key, dynamic value) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
if (value is bool) {
sharedPrefs.setBool(key, value);
} else if (value is String) {
sharedPrefs.setString(key, value);
} else if (value is int) {
sharedPrefs.setInt(key, value);
} else if (value is double) {
sharedPrefs.setDouble(key, value);
} else if (value is List<String>) {
sharedPrefs.setStringList(key, value);
}
}
}
Let's say I have something like this:
return FutureBuilder(
future: _loadingDeals,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return RefreshIndicator(
onRefresh: _handleRefresh,
...
)
}
)
In the _handleRefresh method, I want to programmatically trigger the re-run of the FutureBuilder.
Is there such a thing?
The use case:
When a user pulls down the refreshIndicator, then the _handleRefresh simply makes the FutureBuilder rerun itself.
Edit:
Full code snippet end to end, without the refreshing part. I've switched to using the StreamBuilder, how will the refreshIndicator part fit in all of it?
class DealList extends StatefulWidget {
#override
State<StatefulWidget> createState() => new _DealList();
}
class _DealList extends State<DealList> with AutomaticKeepAliveClientMixin {
// prevents refreshing of tab when switch to
// Why? https://stackoverflow.com/q/51224420/1757321
bool get wantKeepAlive => true;
final RestDatasource api = new RestDatasource();
String token;
StreamController _dealsController;
#override
void initState() {
super.initState();
_dealsController = new StreamController();
_loadingDeals();
}
_loadingDeals() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
this.token = prefs.getString('token');
final res =
this.api.checkInterests(this.token).then((interestResponse) async {
_dealsController.add(interestResponse);
return interestResponse;
});
return res;
}
_handleRefresh(data) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final token = prefs.getString('token');
await this.api.checkInterests(token).then((interestResponse) {
_dealsController.add(interestResponse);
});
return null;
}
#override
Widget build(BuildContext context) {
super.build(context); // <-- this is with the wantKeepAlive thing
return StreamBuilder(
stream: _dealsController.stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
...
}
if (snapshot.connectionState != ConnectionState.done) {
return Center(
child: CircularProgressIndicator(),
);
}
if (!snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
return Text('No deals');
}
if (snapshot.hasData) {
return ListView.builder(
physics: const AlwaysScrollableScrollPhysics(),
itemCount: snapshot.data['deals'].length,
itemBuilder: (context, index) {
final Map deal = snapshot.data['deals'][index];
return ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DealsDetailPage(
dealDetail: deal,
),
),
);
},
title: Text(deal['name']),
subtitle: Text(deal['expires']),
);
},
),
}
},
);
}
}
Why not using a StreamBuilder and a Stream instead of a FutureBuilder?
Something like that...
class _YourWidgetState extends State<YourWidget> {
StreamController<String> _refreshController;
...
initState() {
super...
_refreshController = new StreamController<String>();
_loadingDeals();
}
_loadingDeals() {
_refreshController.add("");
}
_handleRefresh(data) {
if (x) _refreshController.add("");
}
...
build(context) {
...
return StreamBuilder(
stream: _refreshController.stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
return RefreshIndicator(
onRefresh: _handleRefresh(snapshot.data),
...
)
}
);
}
}
I created a Gist with the Flutter main example using the StreamBuilder, check it out
Using StreamBuilder is a solution, however, to trigger the FutureBuilder programmatically, just call setState, it'll rebuild the Widget.
return RefreshIndicator(
onRefresh: () {
setState(() {});
},
...
)
I prefer FutureBuilder over StreamBuilder since I am using Firestore for my project and you get billed by reads so my solution was this
_future??= getMyFuture();
shouldReload(){
setState(()=>_future = null)
}
FutureBuilder(
future: _future,
builder: (context, snapshot){
return Container();
},
)
and any user activity that needs you to get new data simply call shouldReload()