Flutter - Is it possible to show the number of users online? - firebase

I want to show how many people are using my mobile application instantly in my application. For example: "342 people are currently using the application." or "342 people are online right now." I could not find a solution for this.
I store users data with Firebase. So what I want to do is possible by extracting data from the firebase?

You're simplest and most cost effective way, is to create a document, put in a collection for example called .collection(general), when a user logsIn, add 1 to that value, when they logout, subtract 1, and put this in a stream builder.
After success login, run the following function
await FirebaseFirestore.instance
.collection('general')
.doc('onlineCount)
.update({'membersOnline': FieldValue.increment(1)})//this will increase the number by 1.
);
On logout, substract 1.

this is very easy to handle this logic just save the status when users open your app for eg: on homepage and when they kill your app just update that collection to that particular is offline and at the and do query
where(user:online)
and check the number of users you got and simply show that number.
I hope you got this logic.

A little late to the party. But I would personally recommend making use of the App Lifecycle. Meaning:
detached: The application is still hosted on a flutter engine but is detached from any host views.
inactive: The application is in an inactive state and is not receiving user input. For example during a phone call.
paused: The application is not currently visible to the user and running in the background. This is when you press the Home button.
resumed: The application is visible and responding to user input. In this state, the application is in the foreground.
So you will have to create a StatefulWidget and WidgetsBindingObserver:
import 'package:flutter/material.dart';
class LifeCycleManager extends StatefulWidget {
LifeCycleManager({Key key, #required this.child}) : super(key: key);
final Widget child;
#override
_LifeCycleManagerState createState() => _LifeCycleManagerState();
}
class _LifeCycleManagerState extends State<LifeCycleManager> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('AppLifecycleState: $state');
}
#override
Widget build(BuildContext context) {
return widget.child;
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
And then just check the states as follows:
AppLifecycleState _appLifecycleState;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_appLifecycleState = state;
});
if(state == AppLifecycleState.paused) {
print('AppLifecycleState state: Paused audio playback');
//update user file eg. online_status: offline
}
if(state == AppLifecycleState.resumed) {
print('AppLifecycleState state: Resumed audio playback');
//update user file eg. online_status: online
}
print('AppLifecycleState state: $state');
}

Related

Flutter FutureBuilder inside StreamBuilder gets rebuild

Title might be a little off topic. I would like to create a profile page which users can see which posts they liked. My Likes structure in Firebase is like this. It has liked post's id and post's DocumentReference. I store the Post's DocumentReference because:
Prevent duplicate data.
Post's owner can change their name, it would not be affected in the duplicate data.
Hard to maintain like count of the Post.
Users should be able to unlike the posts from this page. When they unlike, it should be directly seen in the page (PostTile will be removed from ListView). To create this behaviour I use StreamBuilder as stream value as below:
Stream<QuerySnapshot> likedQuotes(String uid, int limit, Timestamp creationTime) {
return _likesCollection
.where('userId', isEqualTo: uid)
.orderBy('creationTime', descending: true)
.limit(limit)
.snapshots();
}
To not load all of the posts I had to limit the snapshot and load new data whenever user scrolls down to near end. Should I increase the limit or add .startAfter([creationTime]) condition to query?
For now, I increase the limit and this causes every PostTile to rebuild. Also when all of the data is loaded, whenever user scrolls the ListView, PostTiles in the ListView get rebuild.
To build the PostTile I use FutureBuilder as future value as below:
class PostTileFromDocument extends StatefulWidget {
final Stream docRef;
final String uid;
PostTileFromDocument({this.uid, this.docRef});
#override
_PostTileFromDocumentState createState() => _PostTileFromDocumentState();
}
class _PostTileFromDocumentState extends State<PostTileFromDocument> {
Future tileFuture;
#override
void initState() {
tileFuture = widget.docRef.get();
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: tileFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
currentQuote = PostData.fromSnaphot(snapshot.data);
return Card(...);
}
}
);
}
}
Should I change the structure of the data in Firebase? Is my way of building PostTiles are wrong (FutureBuilder inside StreamBuilder)? What is the proper way of doing what I am trying to do?
Thanks in advance.

How do I track Flutter screens in Firebase analytics?

I have a Flutter app and I'm testing Google Analytics for Firebase on Flutter.
I wanted to see the routes our users (well, me for now) are visiting. I followed the setup steps in firebase_analytics and I checked their example app, too. I enabled debugging for Analytics as described in the Debug View docs
Unfortunately, the only two kinds of screen views (firebase_screen_class) I receive in my Analytics Debug view are Flutter and MainActivity.
I'd expect to see /example-1, /example-2 and /welcome somewhere, but I don't.
This is the app I'm running in Flutter
class App extends StatelessWidget {
final FirebaseAnalytics analytics = FirebaseAnalytics();
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: <String, WidgetBuilder>{
'/example-1': (_) => Example1(),
'/example-2': (_) => Example2(),
'/welcome': (_) => Welcome(),
},
home: Welcome(),
navigatorObservers: [FirebaseAnalyticsObserver(analytics: analytics)],
);
}
}
This exact use-case is in the documentation for Firebase Analytics under the Track Screenviews section.
Manually tracking screens is useful if your app does not use a separate UIViewController or Activity for each screen you may wish to track, such as in a game.
This is exactly the case with Flutter, as Flutter is taking care of the screen updates: most simple Flutter apps run one single FlutterActivity/FlutterAppDelegate and it takes care of rendering different screens on its own, so letting Firebase Analytics automatically track screens will not bring the desired effect.
As far as my past experience goes, the FirebaseAnalyticsObserver was not very helpful, however, I recommend you, too, check their docs again, they do imply that things should "just work". My best guess is that it didn't work well for me because I didn't use RouteSettings on any of my routes *.
In case FirebaseAnalyticsObserver won't work or apply for your app, the next approach worked quite well for me over the past months of development.
You can set the current screen with FirebaseAnalytics at any point, if you call the setCurrentScreen method with the screen name:
import 'package:firebase_analytics/firebase_analytics.dart';
// Somewhere in your widgets...
FirebaseAnalytics().setCurrentScreen(screenName: 'Example1');
As a first attempt I did this in the widget constructor, but that will not work well and miscount the events: if you pop or push routes, all widget constructors in the stack will be called, even though only the top route really qualifies as "the current screen".
To solve this, we need to use the RouteAware class and only set the current screen in case it's the top route: either our route is added to the stack or the previous top route was popped and we arrived onto the route.
RouteAware comes with boilerplate code and we don't want to repeat that boilerplate for all of our screens. Even for small apps, you have tens of different screens, so I created the RouteAwareAnalytics mixin:
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/widgets.dart';
// A Navigator observer that notifies RouteAwares of changes to state of their Route
final routeObserver = RouteObserver<PageRoute>();
mixin RouteAwareAnalytics<T extends StatefulWidget> on State<T>
implements RouteAware {
AnalyticsRoute get route;
#override
void didChangeDependencies() {
routeObserver.subscribe(this, ModalRoute.of(context));
super.didChangeDependencies();
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPop() {}
#override
void didPopNext() {
// Called when the top route has been popped off,
// and the current route shows up.
_setCurrentScreen(route);
}
#override
void didPush() {
// Called when the current route has been pushed.
_setCurrentScreen(route);
}
#override
void didPushNext() {}
Future<void> _setCurrentScreen(AnalyticsRoute analyticsRoute) {
print('Setting current screen to $analyticsRoute');
return FirebaseAnalytics().setCurrentScreen(
screenName: screenName(analyticsRoute),
screenClassOverride: screenClass(analyticsRoute),
);
}
}
I created an enum to track the screens (and functions to turn the enum to screen names). I used the enums to be able to easily track all routes, refactor route names. Using these enums and functions, I can unit test all possible values and enforce consistent naming: no accidental spaces or special characters, no inconsistent capitalization. There could be other, better ways to determine screen class values, but I went with this approach.
enum AnalyticsRoute { example }
String screenClass(AnalyticsRoute route) {
switch (route) {
case AnalyticsRoute.example:
return 'ExampleRoute';
}
throw ArgumentError.notNull('route');
}
String screenName(AnalyticsRoute route) {
switch (route) {
case AnalyticsRoute.example:
return '/example';
}
throw ArgumentError.notNull('route');
}
Next step in the inital setup is to register the routeObserver as a navigatorObserver of your MaterialApp:
MaterialApp(
// ...
navigatorObservers: [
routeObserver,
// FirebaseAnalyticsObserver(analytics: FirebaseAnalytics()),
],
);
Finally, we can add our first example route that's tracked. Add the with RouteAwareAnalytics to your states and override get route.
class ExampleRoute extends StatefulWidget {
#override
_ExampleRouteState createState() => _ExampleRouteState();
}
class _ExampleRouteState extends State<ExampleRoute> with RouteAwareAnalytics{
#override
Widget build(BuildContext context) => Text('Example');
#override
AnalyticsRoute get route => AnalyticsRoute.example;
}
Every time you add a new route, you can do so with little effort: first, add a new enum value, then the Dart compiler will guide you what to add next: add the screen name and class override values in their respective switch-case. Then, find your state that's building your route, add with RouteAwareAnalytics, and add the route getter.
* The reason why I didn't use RouteSettings is that I prefer Simon Lightfoot's approach with the typed arguments instead of the Object arguments the settings provide:
class ExampleRoute extends StatefulWidget {
const ExampleRoute._({#required this.integer, Key key}) : super(key: key);
// All types of members are supported, but I used int as example
final int integer;
static Route<void> route({#required int integer}) =>
MaterialPageRoute(
// I could add the settings here, though, it wouldn't enforce good types
builder: (_) => ExampleRoute._(integer: integer),
);
// ...
}
Add a Navigation Observer
Add Firebase analytics navigation observer to your MatetialApp:
class MyApp extends StatelessWidget {
FirebaseAnalytics analytics = FirebaseAnalytics();
...
MaterialApp(
home: MyAppHome(),
navigatorObservers: [
FirebaseAnalyticsObserver(analytics: analytics), // <-- here
],
);
That's it! Your analytics should appear in the DebugView:
NOTE!
If it's the first time that you are integrating analytics in your app, it will take about a day for your analytics to appear in your dashboard.
See results right away
To see debug results right away, run the above command on your terminal, then check that they appear in the DebugView:
adb shell setprop debug.firebase.analytics.app [your_app_package_name]
Enjoy!
I experienced the issue for some time and was just able to make it work
The issue for me is that I'm not properly passing settings in MaterialPageRoute
return MaterialPageRoute(
settings: RouteSettings(
name: routeName,
),
builder: (_) => viewToShow);
}
I follow the tutorial on FilledStack and was able to figure out my issue after seeing the sample code
If you are seeing "Flutter" in the firebase_screen_class parameter of the screen_view
event, it means you have it configured properly.
You should find the values you are expecting in the firebase_screen parameter, instead of the firebase_screen_class.
It's also worth checking the firebase_previous_screen parameter to see what was the screen that was open before that one.

How to cancel firebase async requests in flutter instead of checking mounted

I have a flutter app talking to the Firebase Realtime Database. I get the data asynchronously, obviously, but my UI allows the user to move to a different part of the app, which means by the time the request completes, the Widget may be unmounted. Best practices say to cancel the async work instead of checking the mounted property but I cannot seem to figure out how to do this for some reason.
#override void initState() {
super.initState();
firebaseRealtimeReference.child('myData').once().then((results) {
if (mounted) {
setState(() {
_myLocalData = results;
}
}
}
/* Alternately with async/await: */
_myLocalData = firebaseRealtimeDatabaseReference.child('myData').once();
}
#override Widget build(BuildContext context) {
return new MyWidget(_myLocalData);
}
#override dispose() {
// Instead of checking mounted in the future, I should instead
// cancel the work in progress here.
super.dispose();
}

Change user with Firebase Google user authentication

When using Firebase Google user authentication the user is immediately logged in if they have already authorized the application and only logged in to one Google account.
Is there a way to force the "Choose an account" dialog to appear so that the user has the opportunity to login to a different Google account or create a new one?
Currency as far as I know the user has to manually logout of the current Google account (or login to > 1) from Google.com to make the dialog appear.
You can force to choose an account with 'prompt' provider parameter.
var googleAuthProvider = new firebase.auth.GoogleAuthProvider();
googleAuthProvider.setCustomParameters({
prompt: 'select_account'
});
firebase.auth().signInWithRedirect(googleAuthProvider)
Tested with Firebase JavaScript SDK v4.1.2
You should sign out from Google explicitly:
Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(status -> {
mFirebaseAuth.signOut();
});
Found the solution here
I'm trying to figure out the same thing. According to some Google documentation, it appears that you can force the account chooser with a "prompt" command (of "none", "select_account" or "consent"):
Force google account chooser
...however there appears to be no way to set the "prompt" value in any of Firebase's authentication methods (specifically authWithOAuthRedirect and authWithOAuthPopup).
Were you ever able to figure it out?
In my following code, the gooogle sign-in button every time prompts for choosing account...
public class MainActivity extends AppCompatActivity {
Button btn_signOut;
private GoogleSignInClient mGoogleSignInClient;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_signOut = findViewById(R.id.btnSignOut);
btn_signOut.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
signOut();
}
});
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build();
mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
}
private void signOut() {
mGoogleSignInClient.signOut()
.addOnCompleteListener(this, new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
finish();
}
});
}
}
Use this way to signout.
Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(new
ResultCallback<Status>()
{
#Override
public void onResult(#NonNull Status status)
{
mAuth.signOut();
}
});
In flutter, use: GoogleSignIn().signOut();
(assumming you used the google_sign_in package)

Firebase - Keep user logged in when they close app [duplicate]

Thank to Firebase the user can logged with the help of G+, Facebook or Twitter. When they are logged, everything is fine.
When the Android app is closed and re-opened, how to reenable the previous succeed logged user with the Firebase API. It is not explained neither in the app demo or in the documentation.
For exemple for Facebook, the sdk seems to save the token, that 's why the button is at connected state (showing that you can disconnect). But what about Firebase and for other authentication systems.
Thanks to the #Frank-van-Puffelen answer, I had some trials until I get something relevant (at least for me : comment are welcome to improve).
I have based my OAuth architecture into 3 mains components :
fdsfds
One single AuthStateListener that is located in the Application.
One Utils Singleton OAuthManager that deal with all authentication process
One or Many Activities that deals with Authentification user interaction (Signin Buttons and so on)
Application Class
FacebookSdk.sdkInitialize(this);
Firebase.setAndroidContext(this);
Firebase.getDefaultConfig().setLogLevel(Logger.Level.DEBUG);
Firebase.getDefaultConfig().setPersistenceEnabled(true);
Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.addAuthStateListener(new Firebase.AuthStateListener() {
#Override
public void onAuthStateChanged(AuthData authData) {
if (authData != null) {
// user is logged in
// create a partialUser from authData
OAuthManager.getDefault().setAuthenticatedUser(authData);
// fetch, merge and save back the partialUser with server registerUser.
OAuthManager.getDefault().startFetchingUserInfo();
} else {
// user is not logged in
// Try to retrieve the user from Facebook SDK
// Try to retrieve the user from "Token and Id save in Android Preferences (in case of issue, or cache reset from Firebase))
// In retrieve is not possible, clean auth data
OAuthManager.getDefault().retrieveOAuth(MilleZimU.getInstance());
}
}
});
OAuthManager
Here is every services that deal with authentication (part has been copy from the Firebase dedicated demo activity)
SignInActivity
Here only remain the part that deal with UI interaction.
Retreiving ?
I'm not sure this is necessary, but case to case (maybe due to crash or update of the app), authentication status where different from Firebase|Prefs|FacebookSdk. I will see with time.
You'll need to add a AuthStateListener. This is described in the Firebase documentation on Monitoring Authentication. From there:
Firebase ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
ref.addAuthStateListener(new Firebase.AuthStateListener() {
#Override
public void onAuthStateChanged(AuthData authData) {
if (authData != null) {
// user is logged in
} else {
// user is not logged in
}
}
});
For anything related to Firebase Authentication on Android, the dedicated demo app is a great next stop. But be sure to first read the documentation, they're not half bad as far as docs go.
Create a BaseActivity class and make sure all other Activities in the app extends that class. The use 'instanceOf' to send user to LoginActivity if authData is null from AuthListener.
package com.mabiri.mabiristores;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
import com.firebase.client.AuthData;
import com.firebase.client.Firebase;
import com.mabiri.mabiristores.login.CreateAccount2Activity;
import com.mabiri.mabiristores.login.CreateAccountActivity;
import com.mabiri.mabiristores.login.LoginActivity;
import com.mabiri.mabiristores.login.MapsActivity;
import com.mabiri.mabiristores.utils.Utils;
public class BaseActivity extends AppCompatActivity {
protected Firebase.AuthStateListener mAuthListener;
protected Firebase mFirebaseRef;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mFirebaseRef = new Firebase(YOUR_FIREBASE_URL);
if (!((this instanceof LoginActivity) || (this instanceof CreateAccountActivity)
|| (this instanceof CreateAccount2Activity) || (this instanceof MapsActivity))) {
mAuthListener = new Firebase.AuthStateListener() {
#Override
public void onAuthStateChanged(AuthData authData) {
/* The user has been logged out */
if (authData == null) {
//Stop services and clear sharedPreferences if any
/*Take user to login screen*/
takeUserToLoginScreenOnUnAuth();
}
}
};
mFirebaseRef.addAuthStateListener(mAuthListener);
}
}
#Override
protected void onResume() {
super.onResume();
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
super.onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
/* Inflate the menu; this adds items to the action bar if it is present. */
getMenuInflater().inflate(R.menu.menu_base, menu);
return true;
}
private void takeUserToLoginScreenOnUnAuth() {
/** Move user to LoginActivity, and remove the backstack */
Intent intent = new Intent(BaseActivity.this, LoginActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
protected void logout() {
/**Unauthenticate user from firebase*/
mFirebaseRef.unauth();
}
/**
* Show error toast to users
*/
protected void showErrorToast(Context context, String message) {
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
}
Yup I have struggle with this as well, but a quick update on this issue, you cannot use the (and please correct me if i'm wrong):
Firebase ref
anymore, what you should do is declare as a global
private FirebaseAuth mAuth;
and then use this object on the listener:
mAuth = FirebaseAuth.getInstance();
and only after that you can use the listener
mAuth.addAuthStateListener(mAuthListener);

Resources