I'm trying to publish a pub/sub message in my flutter application like this:
import 'package:flutter/material.dart';
import 'package:googleapis/pubsub//v1.dart';
import 'package:googleapis_auth/auth_io.dart';
const _SCOPES = const [PubsubApi.PubsubScope];
class Activities extends StatefulWidget {
Activities();
#override
_Activities createState() => _Activities();
}
final _credentials = new ServiceAccountCredentials.fromJson(r'''
{
"type": "service_account",
...
}
''');
class _Activities extends State<Activities> with SingleTickerProviderStateMixin {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Center(
child: Container(
child: new MaterialButton(
onPressed: () {
debugPrint("trying to publish a message...");
clientViaServiceAccount(_credentials, _SCOPES)
.then((http_client) {
var pubSubClient = new PubsubApi(http_client);
Map<String, dynamic> message = new Map<String, dynamic>();
message["data"] = Map<String, dynamic>();
message["data"]["type"] = "foo";
message["data"]["senderId"] = "bar";
pubSubClient.projects.topics
.publish(new PublishRequest.fromJson(message), "projects/myProject/topics/events")
.then((publishResponse) {
debugPrint(publishResponse.toString());
}).catchError((e,m){
debugPrint(e.toString());
});
})
.catchError((e,m) {
debugPrint(e.toString());
});
},
child: new Text("Publish message"),
),
),
)
);
}
}
But in the logs I get the following error message:
I/flutter ( 5281): DetailedApiRequestError(status: 400, message: The value for 0 is too small. You passed message_count in the request, but the minimum value is 1.)
I googled this message but did not found anything. I think my message structure is maybe wrong, but maybe it's something else ? Don't wanna lose too much time on it...
Ok, for those who wonder, here is what I have come up with:
var messages = {
'messages': [
{
'data': base64Encode(utf8.encode('{"foo": "bar"}')),
},
]
};
pubSubClient.projects.topics
.publish(new PublishRequest.fromJson(messages), "your topic")
.then((publishResponse) {
debugPrint(publishResponse.toString());
}).catchError((e,m){
debugPrint(e.toString());
});
Maybe someone should write an article on it, this was not very clear and I had to try different things and also read the source from the pubsub client library...
Use gcloud: ^0.7.3 package.
Use the below code to establish a connection.
import 'package:gcloud/pubsub.dart';
import 'package:googleapis/pubsub/v1.dart';
import 'package:googleapis_auth/auth_io.dart';
// Read the service account credentials from the file.
var jsonCredentials = new File('my-project.json').readAsStringSync();
var credentials = new auth.ServiceAccountCredentials.fromJson(jsonCredentials);
var client = await auth.clientViaServiceAccount(credentials, scopes);
var pubsub = new PubSub(client, 'my-project');
For the Publish and Subscribe refer to this section of the readme.
Related
I currently have a simple API but my flutter widgets could not detect the changes happening in the API without restarting the whole application.
The API was made as a test API on https://www.mockable.io/. I managed to read from this API using a StreamBuilder. And still it does not change the values in the widget whenever i update their value in the API.
Default JSON
{
"id": "123",
"token": "1token",
"status": "Valid"
}
After Value Change
{
"id": "123",
"token": "1token",
"status": "Not Valid"
}
My StreamBuilder Code
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
final String url = "http://demo2722084.mockable.io/user";
class StreamPage extends StatefulWidget {
#override
StreamPageState createState() {
return new StreamPageState();
}
}
class StreamPageState extends State<StreamPage> {
StreamController _userController;
Future fetchUser() async{
final response = await http.get(url);
if(response.statusCode == 200){
return json.decode(response.body);
}else{
print("Exception caught: Failed to get data");
}
}
loadUser() async{
fetchUser().then((res) async{
_userController.add(res);
return res;
});
}
#override
void initState() {
_userController = new StreamController();
loadUser();
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Stream"),
),
body: new StreamBuilder(
stream: _userController.stream,
builder: (context, snapshot){
if(snapshot.hasError){
print("Exception: ${snapshot.error}");
}
if(snapshot.hasData){
var user = snapshot.data;
var id = user["id"] ?? "";
var token = user["token"] ?? "";
var status = user["status"] ?? "";
return new Center(
child: new ListTile(
title: new Text("Token: $token"),
subtitle: new Text("Status: $status"),
leading: new Text(id),
),
);
}
if(snapshot.connectionState != ConnectionState.waiting){
return new Center(
child: new CircularProgressIndicator(),
);
}
if(!snapshot.hasData && snapshot.connectionState != ConnectionState.done){
return new Center(
child: new CircularProgressIndicator(),
);
}
},
),
);
}
}
I expected the StreamBuilder to pick up the changed status value but the app has to be restarted for the new value to be reflected in the app.
Is there an example of how I can achieve this properly since this is quite frankly my first time trying out a StreamBuilder.
Your HTTP request results in just one data point, so for that you could use a FutureBuilder. But you want to know when something changes at the server. Your HTTP connection has finished when the change is made and so has no idea that it has happened.
There are several ways to detect changes at the server, for example, polling, user-initiated refresh, websockets, etc. For demonstration purposes, instead of calling loadUser in initState you could start a 1 second periodic timer to do that, which would update the stream every second and you should see the change. (Don't forget to cancel the timer in dispose.)
That's unlikely to be viable in production, so explore websockets, etc for pushed notifications from the web server. Also explore whether Firebase Cloud Messaging is a better way to send notification of changes to your application.
#override
void initState() {
super.initState();
_userController = StreamController();
Timer.periodic(Duration(seconds: 1), (_) => loadUser());
}
you probably also need to do this only while the app is in foreground. You can use my LifecycleAwareStreamBuilder, there is an example on how to use it here
I am creating a subscription with GraphQL, and I need to consume that subscription with Flutter, but I don't know how to do that, the thing that I need is something like a UI component that would be tied to a subscription and it will be automatically refreshed.
I will appreciate any feedback.
My GraphqlServer runs a subscription named getLogs which returns the log information in the following format.
{
"data": {
"getLogs": {
"timeStamp": "18:09:24",
"logLevel": "DEBUG",
"file": "logger.py",
"function": "init",
"line": "1",
"message": "Hello from logger.py"
}
}
}
If you are like me who wants to use only the GraphQL Client directly, then following sample could help.
import 'package:graphql/client.dart';
import 'package:graphql/internal.dart';
import 'package:flutter/material.dart';
import 'dart:async';
class LogPuller extends StatefulWidget {
static final WebSocketLink _webSocketLink = WebSocketLink(
url: 'ws://localhost:8000/graphql/',
config: SocketClientConfig(
autoReconnect: true,
),
);
static final Link _link = _webSocketLink;
#override
_LogPullerState createState() => _LogPullerState();
}
class _LogPullerState extends State<LogPuller> {
final GraphQLClient _client = GraphQLClient(
link: LogPuller._link,
cache: InMemoryCache(),
);
// the subscription query should be of the following format. Note how the 'GetMyLogs' is used as the operation name below.
final String subscribeQuery = '''
subscription GetMyLogs{
getLogs{
timeStamp
logLevel
file
function
line
message
}
}
''';
Operation operation;
Stream<FetchResult> _logStream;
#override
void initState() {
super.initState();
// note operation name is important. If not provided the stream subscription fails after first pull.
operation = Operation(document: subscribeQuery, operationName: 'GetMyLogs');
_logStream = _client.subscribe(operation);
}
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _logStream,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: Container(
child: CircularProgressIndicator(
strokeWidth: 1.0,
),
),
);
}
if (snapshot.hasData) {
return Center(
child: Text(
snapshot.data.data['getLogs']
['message'], // This will change according to you needs.
),
);
}
return Container();
},
);
}
}
As I am using a StreamBuilder to build the widget it will take care of closing the stream. If this is not the case for you, stream.listen() method will return a StreamSubscription<FetchResult> object which you can call the cancel() method which can be done inside dispose() method of a stateful widget or any such method for a standalone Dart client.
You can check the next library https://github.com/zino-app/graphql-flutter
Just find out the bug in this library, Just open the subscription.dart file by ctrl+click on Subscription. In that file, it is easy to see that socketClient variable is null. So just define it in initState() function as shown in the docs. Restart your app. And it works like a charm. So, basically, you just need to initialize that variable in the subscription.dart file.
class Search extends StatefulWidget {
int id;
Search([this.id]);
#override
_SearchState createState() => new _SearchState();
}
class _SearchState extends State<Search> {
#override
void initState() {
super.initState();
}
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
widget.id;
return new Scaffold(
appBar: new AppBar(
actions: <Widget>[
new IconButton(
icon: new Icon(Icons.exit_to_app),
onPressed: _getTicketDetails
),
],
centerTitle: true,
title: new Text
("TicketsDetails", style: const TextStyle(
fontFamily: 'Poppins'
,),
),
),
);
}
_getTicketDetails() async {
print(widget.id);
var userDetails = {};
final response = await http.get(
"https....", headers: {
HttpHeaders.AUTHORIZATION: access_token
});
List returnTicketDetails = json.decode(response.body);
print(returnTicketDetails);
for (var i = 0; i < (returnTicketDetails?.length ?? 0); i++) {
final ticketresponse = await http.get(
"https:...
.toString()}", headers: {
HttpHeaders.AUTHORIZATION:
access_token
});
userDetails[returnTicketDetails[i]["user_id"]] =
json.decode(ticketresponse.body);
}
print(userDetails);
}
}
I would like to display in a Listview the index of my userDeatails,
however for some reason the compiler does not recognise the userDetails,
hence it highlight it as an error. I have done this before, but I
don't get why I am encountering this issue now.
At the moment when I run it only display the appBar
As mentioned in the comments, your userDetails variable is scoped inside the _getTicketDetails method. You need to declare it outside of that method if you want it visible to the rest of your class:
var userDetails = {}; // Moved outside
_getTicketDetails() async {
...
}
Though note that you should also call setState when you modify this variable so that Flutter knows that this widget has changed and needs to be rebuild/rendered.
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/
In my app, I have a drawer with a UserAccountsDrawerHeader, which I feed its properties by simply getting the x property from FirebaseAuth.instance.currentUser.x
In the latest firebase_auth 0.2.0 version , where currentUser() is async.
I have been trying for several hours to store the information of the currently logged user and have not yet reached the correct way to do this.
I understand that I can access them by something like the following:
Future<String> _getCurrentUserName() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user.displayName;
}
...
new UserAccountsDrawerHeader(accountName: new Text(_getCurrentUserName()))
I understand that these code snippets will give type mismatch, but I am just trying to illustrate what I am trying to do.
What am I missing exactly that is preventing me from reaching a solution?
Update
class _MyTabsState extends State<MyTabs> with TickerProviderStateMixin {
TabController controller;
Pages _page;
String _currentUserName;
String _currentUserEmail;
String _currentUserPhoto;
#override
void initState() {
super.initState();
_states();
controller = new TabController(length: 5, vsync: this);
controller.addListener(_select);
_page = pages[0];
}
My method
I just coupled the auth state with my previously implemented TabBar state
_states() async{
var user = await FirebaseAuth.instance.currentUser();
var name = user.displayName;
var email = user.email;
var photoUrl = user.photoUrl;
setState(() {
this._currentUserName=name;
this._currentUserEmail=email;
this._currentUserPhoto=photoUrl;
_page = pages[controller.index];
});
}
My Drawer
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new UserAccountsDrawerHeader(accountName: new Text(_currentUserName) ,
accountEmail: new Text (_currentUserEmail),
currentAccountPicture: new CircleAvatar(
backgroundImage: new NetworkImage(_currentUserPhoto),
),
Here is the exception I get from the debug console
I/flutter (14926): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (14926): The following assertion was thrown building MyTabs(dirty, state: _MyTabsState#f49aa(tickers:
I/flutter (14926): tracking 1 ticker)):
I/flutter (14926): 'package:flutter/src/widgets/text.dart': Failed assertion: line 207 pos 15: 'data != null': is not
I/flutter (14926): true.
I/flutter (14926): Either the assertion indicates an error in the framework itself, or we should provide substantially
Update 2:
This is how I modified the google sign in function from the firebase examples:
Future <FirebaseUser> _testSignInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
//checking if there is a current user
var check = await FirebaseAuth.instance.currentUser();
if (check!=null){
final FirebaseUser user = check;
return user;
}
else{
final FirebaseUser user = await _auth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
assert(user.email != null);
assert(user.displayName != null);
assert(!user.isAnonymous);
assert(await user.getToken() != null);
return user;
}
}
Update 3:
My main function
void main() {
runApp(
new MaterialApp(
home: new SignIn(),
routes: <String, WidgetBuilder>{
"/SignUp":(BuildContext context)=> new SignUp(),
"/Login": (BuildContext context)=> new SignIn(),
"/MyTabs": (BuildContext context)=> new MyTabs()},
));
}
And then my SignIn contains a google button that when pressed:
onPressed: () { _testSignInWithGoogle(). //async returns FirebaseUser
whenComplete(()=>Navigator.of(context).pushNamed("/MyTabs")
);
}
and the Drawer from update 1 is included within MyTabs build.
There are several possibilities.
First : Use a stateful widget
Override the initState method like this :
class Test extends StatefulWidget {
#override
_TestState createState() => new _TestState();
}
class _TestState extends State<Test> {
String _currentUserName;
#override
initState() {
super.initState();
doAsyncStuff();
}
doAsyncStuff() async {
var name = await _getCurrentUserName();
setState(() {
this._currentUserName = name;
});
}
#override
Widget build(BuildContext context) {
if (_currentUserName == null)
return new Container();
return new Text(_currentUserName);
}
}
Second : Use the FutureBuilder widget
Basically, it's a wrapper for those who don't want to use a stateful widget. It does the same in the end.
But you won't be able to reuse your future somewhere else.
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new FutureBuilder(
future: _getCurrentUserName(),
builder: (context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData)
return new Text(snapshot.data.toString());
else
return new Container();
},
);
}
}
Explanation :
Your getCurrentUserName is asynchronous.
You can't just directly mix it with other synchronous functions.
Asynchronous functions are quite useful. But if you want to use them, just remember two things :
Inside another async function, you can var x = await myFuture, which will wait until myFuture finish to get it's result.
But you can't use await inside a sync function.
Instead, you can use
myFuture.then(myFunction) or myFuture.whenComplete(myFunction). myFunction will be called when the future is finished. And they both .then and .whenComplete will pass the result of your future as parameter to your myFunction.
"How to properly implement authentification" ?
You should definitely not do it this way. You'll have tons of code duplication.
The most ideal way to organise layers such as Authentification is like this :
runApp(new Configuration.fromFile("confs.json",
child: new Authentification(
child: new MaterialApp(
home: new Column(
children: <Widget>[
new Text("Hello"),
new AuthentifiedBuilder(
inRoles: [UserRole.admin],
builder: (context, user) {
return new Text(user.name);
}
),
],
),
),
),
));
And then, when you need a configuration or the current user inside a widget, you'd do this :
#override
Widget build(BuildContext context) {
var user = Authentification.of(context).user;
var host = Configuration.of(context).host;
// do stuff with host and the user
return new Container();
}
There are so many advantages about doing this, that there's no reason not to do it.
Such as "Code once, use everywhere". Or the ability to have a generic value and override it for a specific widget.
You'll realise that a lot of Flutter widgets are following this idea.
Such as Navigator, Scaffold, Theme, ...
But "How to do this ??"
It's all thanks to the BuildContext context parameter. Which provides a few helpers to do it.
For exemple, the code of Authentification.of(context) would be the following :
class Authentification extends StatefulWidget {
final Widget child;
static AuthentificationData of(BuildContext context) {
final AuthentificationData auth = context.inheritFromWidgetOfExactType(AuthentificationData);
assert(auth != null);
return auth;
}
Authentification({this.child});
#override
AuthentificationState createState() => new AuthentificationState();
}