Use multiple firebase accounts in single android app for google analytics - android

I have a use case in which 1 app will be used by multiple separate companies (franchises) that will have their own marketing and management teams. I need the user to select a franchise when the mobile app starts up, and then from that point on, push all analytics data to that franchise's firebase (google analytics) account. Similarly any push notifications that are sent from that franchise's servers need to go to the user.
Is this configuration possible? In the past I used to set up a google analytics account for each franchise and just download the UA-xxx number from the server on franchise selection, and then set up the google analytics object based on that..
What is the appropriate way to achieve this via firebase connected to google analytics ?
I found the offical API reference: https://firebase.google.com/docs/configure/
This link explains how to do it for iOS but doesn't mention how to do it in android. It does say however that the firebase init runs before user code.. perhaps that means it is not possible?
Here is the init provider they mention: https://firebase.google.com/docs/reference/android/com/google/firebase/provider/FirebaseInitProvider

create for each new firebase app
FirebaseApp firebaseApp =
FirebaseApp.initializeApp(Context, FirebaseOptions,firebaseAppName);
you can create firebase app by passing options:
https://firebase.google.com/docs/reference/android/com/google/firebase/FirebaseOptions.Builder
FirebaseOptions options = new FirebaseOptions.Builder()
.setApiKey(String)
.setApplicationId(String)
.setDatabaseUrl(String)
.build();
then when You want to use Analytics you need to set default one by call:
FirebaseApp firebaseApp =
FirebaseApp.initializeApp(Context, FirebaseOptions,"[DEFAULT]");
keep in mind that only this DEFAULT firebase app will be used in analytics
but first off all you need to remove init provider in manifest
<!--remove firebase provider to init manually -->
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove"/>
and init default firebase app manually!
example how to send event via default firebase app(after initialized):
// get tracker instance
FirebaseAnalytics trakerInstance = FirebaseAnalytics.getInstance(context);
// create bundle for params
Bundle params = new Bundle();
// put param for example action
params.putString(ACTION_KEY, eventAction);
// send event
trackerInstance.logEvent(eventCategory, params);
#ceph3us I tried your solution, and it didn't work for me. If I
initialise firebase at runtime as you suggested then I get an error:
Missing google_app_id. Firebase Analytics disabled. Are you sure it is
working? – rMozes
first of all
did you removed default provider by putting in manifest tools:node="remove"?
did u initialized ['DEFAULT'] firebase app as i described
did you check if a ['DEFAULT'] firebase app is initialized before sending any event ?
ad 1) the error: Missing google_app_id suggests me that gardle plugin didn't removed provider as expected - and your app is starting a default provider which complains about missing app id
ad 3) don't do any calls relying on firebase app before firebase app is initialized
protected boolean isDefaultFirebaseAppInitialized() {
try {
// try get
return FirebaseApp.getInstance(FirebaseApp.DEFAULT_APP_NAME) != null;
// catch illegal state exc
} catch (IllegalStateException ise) {
// on such case not initialized
return false;
}
}
// check on default app
if(isDefaultFirebaseAppInitialized()) {
// get tracker
FirebaseAnalytics trakerInstance = FirebaseAnalytics.getInstance(context);
// log event
trackerInstance.logEvent(eventCategory, params);
} else {
// postpone
}
#ceph3us 1. I removed the provider as you said, if I wouldn't remove
the provider and try to initialise the default app then I would get a
IllegalStateException about default firebase app already exists. 2. I
initialised default firebase app as you described. 3. Yes, I logged
the app name and app_id and there is a log: AppName: [DEFAULT], Google
app id: valid_app_id But when I want to post something to analytics,
then it says that: Missing google_app_id. Firebase Analytics disabled.
– rMozes
99,99% you are trying to send event before app is initialized ! (see above example)
#ceph3us I initialise FirebaseApp in the onCreate method of
Application subclass. I send event to firebase when a button is
clicked. Anyway I uploaded a test project to github, can you take a
look at it? It is possible I misunderstand something.
github.com/rMozes/TestFirebaseAnalytics – rMozes
try (as initialization is asynchronous - so until you test its initialized you cant send events):
https://github.com/rMozes/TestFirebaseAnalytics/compare/master...c3ph3us:patch-2
if above fails you have two more chances :)
by define the string in xml: as a placeholder
<string name="google_app_id">fuck_you_google</string>
1) change the placeholder id via reflections to other one before any call to init/or use from firebase:
hint how to
2) provide a own text for the placeholder id via own Resources class implementation for Resources.getString(R.string.google_app_id) call:
an example how to achieve it (adding a new resources by id)
if you proper change R field via reflections or substitute a call to Resources.getString(R.string.google_app_id) with own text you will not get message wrong app id: "fuck_you_google"
& good luck :)

It's possible to have multiple Firebase instance in your apps
FirebaseOptions options = new FirebaseOptions.Builder()
.setApplicationId("Your AppId") // Required for Analytics.
.setApiKey("You ApiKey") // Required for Auth.
.setDatabaseUrl("Your DB Url") // If you wanted to
.build();
FirebaseApp.initializeApp(context, options, "CompanyA");
Which you can get Firebase instances by
FirebaseApp appCompanyA = FirebaseApp.getInstance("CompanyA");
You can see the full example use of Auth and Realtime Database using multiple Firebase instance here
I'll leave this solution here which may duplicate with another answers for someone who might need this
Hope this help :)

Related

Android Amplify Cognito Post Confirmation User Table not populating after user is confirmed

The App
Android Studio project with Amplify back end that allows the user to sign in and see some profile info. I want to keep a DynamoDB user table that holds some additional data. I expect to update the table depending on some actions the user takes, however, I really liked this notion of a Lambda function initially adding the user to the table securely and without me having to do it manually.
The Problem
The Lambda function is invoked, but the new user is not added to the generated DynamoDB table. Very frustrating because the only clue I have is a CloudWatch Log (shown below) that I cannot interpret.
I've looked online and found this SO/GitHub issue, which is fairly similar to mine except I did not amplify update auth and set a Post Confirmation trigger there or add any Groups in Cognito. I got no circular dependency error doing amplify push commands.
UPDATE
I get the same outcome when using the Cognito Hosted UI in my desktop web browser. Surely the Lambda function that Amazon published in their docs isn't flawed?
What I need
General guidance on why the user data might not be getting to the DynamoDB table. Should I try to emulate the OP of the linked post above? Is there some intermediate step that I am missing?
Any help is fantastic, and I'm more than happy to provide more resources if they'll help.
Thanks.
What I've done
amplify init and amplify config
amplify add auth following these instructions.
In the AWS console I set up the Cognito Hosted UI to let users signup and login. It works great.
amplify add apifollowing these instructions. Here is the schema.graphql file:
type User #model #auth(rules: [{ allow: owner }]) {
id: ID!
name: String
email: String
}
amplify add function following the same instructions - here is the index.js file:
/* Amplify Params - DO NOT EDIT
API_TBCPAPI_GRAPHQLAPIIDOUTPUT
API_TBCPAPI_USERTABLE_ARN
API_TBCPAPI_USERTABLE_NAME
ENV
REGION
Amplify Params - DO NOT EDIT */
var aws = require('aws-sdk');
var ddb = new aws.DynamoDB();
exports.handler = async (event, context) => {
let date = new Date();
if (event.request.userAttributes.sub) {
let params = {
Item: {
'id': {S: event.request.userAttributes.sub},
'__typename': {S: 'User'},
'name': {S: event.request.userAttributes.name},
'email': {S: event.request.userAttributes.email},
'createdAt': {S: date.toISOString()},
'updatedAt': {S: date.toISOString()},
},
TableName: process.env.API_TBCPAPI_USERTABLE_NAME
};
// Call DynamoDB
try {
await ddb.putItem(params).promise()
console.log("Success");
} catch (err) {
console.log("Error", err);
}
console.log("Success: Everything executed correctly");
context.done(null, event);
} else {
// Nothing to do, the user's email ID is unknown
console.log("Error: Nothing was written to DynamoDB");
context.done(null, event);
}
};
I was careful to edit the proper DynamoDB table name.
amplify push with no errors.
In the AWS Cognito Console, I manually chose my Post Confirmation Lambda from the drop down list. Saved with no errors.
amplify pull to make sure I've got the changes (not sure if this was needed).
Ran the app, created a user in the Hosted UI, confirmed via email code, logged in, and returned to the app.
Went to check the DynamoDB table and there was no entry in the User table. Bummer.
Went to check the Lambda monitor, and it showed 1 invocation, with 100% success rate. (I beg to differ)
Followed the link to the CloudWatch Logs and found this:
2021-05-27T15:47:26.024Z c6d8ddfc-1f67-4bc9-b01c-1b89b91f0abc INFO Error ValidationException: Supplied AttributeValue is empty, must contain exactly one of the supported datatypes
at Request.extractError (/var/runtime/node_modules/aws-sdk/lib/protocol/json.js:52:27)
at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20)
at Request.emit (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:78:10)
at Request.emit (/var/runtime/node_modules/aws-sdk/lib/request.js:688:14)
at Request.transition (/var/runtime/node_modules/aws-sdk/lib/request.js:22:10)
at AcceptorStateMachine.runTo (/var/runtime/node_modules/aws-sdk/lib/state_machine.js:14:12)
at /var/runtime/node_modules/aws-sdk/lib/state_machine.js:26:10
at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:38:9)
at Request.<anonymous> (/var/runtime/node_modules/aws-sdk/lib/request.js:690:12)
at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:116:18) {
code: 'ValidationException',
time: 2021-05-27T15:47:25.983Z,
requestId: 'BRRPE7CIIAGD1H28DVO7UEGISVVV4KQNSO5AEMVJF66Q9ASUAAJG',
statusCode: 400,
retryable: false,
retryDelay: 22.42081554987443
}
Instead of calling context.done(null, event) just return the event return event.
The issue is when you are using an async function the return value is wrapped in a promise. Without a return your promise resolves to undefined which obviously doesn't pass their validation.
The Lambda runtime was set to Node.js 14.x, and I switched it to 12.x to see if that would change anything (maybe the AWS docs were not completely accurate in their instructions).
Anyway that seemed to do it for me, and I got a user loaded into my DynamoDB table.
Update
Okay, so it wasn't the Lambda runtime... For some reason if I eliminate the name, email, and dates attributes in the index.js file then it will successfully put the user in the database. I must have made that change, pushed, and THEN changed the runtime.
Update 2
Problem is event.request.userAttributes.name is empty and a || 'no name' next to it did the trick as suggested in a comment here. The email address actually worked fine.
Honestly, the user id and email are enough for the initial entry. I can add whatever attributes that are necessary later. I'll just use Kotlin and save myself the trouble of trying to debug JavaScript.
Hope this helps someone else trying to navigate these docs for Android.
Thanks.

Can I get the Title/Name or Description of A/B-Test and the Variation-Name from Firebase into Activity?

Got this basic Firebase RemoteConfig A/B-Test running on Android. I want to get the title/name and description of the A/B-Test configurated in Firebase. Also it would be nice to get the name of the variations (Control, Variation A, ...)
How do I get these data?
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// bind XML elements into variables
bindWidgets();
// Only for debugging: get Instance ID token from device
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(new OnCompleteListener<InstanceIdResult>() {
#Override
public void onComplete(#NonNull Task<InstanceIdResult> task) {
String deviceToken = task.getResult().getToken();
Log.wtf("Instance ID", deviceToken);
}
});
// Remote Config Setting
FirebaseRemoteConfigSettings mFirebaseRemoteConfigSettings = new FirebaseRemoteConfigSettings
.Builder()
.setDeveloperModeEnabled(BuildConfig.DEBUG)
.build();
mFirebaseRemoteConfig.setConfigSettings(mFirebaseRemoteConfigSettings);
// Remote Config with HashMap
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("buttonColor", "#999999");
mFirebaseRemoteConfig.setDefaults(hashMap);
final Task<Void> fetch = mFirebaseRemoteConfig.fetch(FirebaseRemoteConfig.VALUE_SOURCE_STATIC);
fetch.addOnSuccessListener(this, new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
mFirebaseRemoteConfig.activateFetched();
// get value of key buttonColor from HashMap
String buttonColor = mFirebaseRemoteConfig.getString("buttonColor");
button.setBackgroundColor(Color.parseColor(buttonColor));
}
});
}
There is no official API to retrieve any information about your A/B test, besides the variant selected.
It's going to be much, much easier to just hardcode the values inside your app, or manually add them on Firebase Hosting / Cloud Firestore.
That being said, here's 2 vague ideas for more automatic solutions, but I really don't recommend trying either!
BigQuery
You could link your project to BigQuery, it will then contain your Analytics data. Specifically:
In this query, your experiment is encoded as a user property with the experiment name in the key and the experiment variant in the value.
Once your data is in BigQuery, you could then retrieve it using the SDKs. You will of course need to handle permissions and access control, and this is almost certainly extremely overkill.
Cloud Functions (and hosting)
Another solution is to just store the data you need elsewhere, and retrieve it. Firebase Cloud Functions have the ability to react to a new remote config (A/B tests use these under the hood). So you could create a function that:
Is triggered on new remote config creation.
Stores a mapping of parameter key to name etc in Cloud Firestore or similar.
Your app could then query this Cloud Firestore / hosted file / wherever you hosted it.
Note: I couldn't actually figure out how to get any info about the remote config in Cloud Functions. Things like version name, update time etc are available, but description seems suspiciously vague.
We wanted to get these informations for our Tracking/Analyzing Tools. So we implemented a workaround and added an additional Remote Config variable abTestName_variantInfo where we set a short info in the A/B-Testing configuration about the name of the A/B-Test and the variant we are running in. With this we can use the main Remote Config variable for the variant changes (e.g layout or functionality) without being dependent of our own naming-convention for tracking.
For example we used the two Remote Config variables ratingTest_variant (values: emojis or stars) and added the variable ratingTest_variantInfo (values: abTest_rating_emojis and abTest_rating_stars).

What will firebase.getInstance() get if there are multiple databases

I was using a firebase database in my Android app. Now I need to use another database but I want to know for the old versions of my app what will "firebaseDatabase.getInstance" return? Which database from my databases will be returned?
FirebaseDatabase.getInstance();
.getInstance() function actually gets a string of url as an input parameter. You can see it here.
Alternatively you can follow the tutorial here to actually create more than one instance in one app with manually setting FirebaseOptions, FirebaseApp and FirebaseDatabase objects.
// Manually configure Firebase Options
FirebaseOptions options = new FirebaseOptions.Builder()
.setApplicationId("1:27992087142:android:ce3b6448250083d1") // Required for Analytics.
.setApiKey("AIzaSyADUe90ULnQDuGShD9W23RDP0xmeDc6Mvw") // Required for Auth.
.setDatabaseUrl("https://myproject.firebaseio.com") // Required for RTDB.
.build();
// Initialize with secondary app.
FirebaseApp.initializeApp(this /* Context */, options, "secondary");
// Retrieve secondary app.
FirebaseApp secondary = FirebaseApp.getInstance("secondary");
// Get the database for the other app.
FirebaseDatabase secondaryDatabase = FirebaseDatabase.getInstance(secondary);
Please also read the notes in the end, as using different databases might confuse your Google Analytics, causing analytics data drops.

Detect Firebase Auth Provider for Loged in User

How can i check that my user Loged in to my app Using which Auth Provider ? I wanted to detect that did my user loged in to my app using Facebook auth provider or using Email Provider or by using Google auth provider . I have searched this in Firebase Docs but i couldnt find any proper answer ,
FirebaseUser firebaseUser = FirebaseAuth.getInstance().getCurrentUser();
if (firebaseUser.getProviderData().size() > 0) {
//Prints Out google.com for Google Sign In, prints facebook.com for Facebook
e("TOM", "Provider: " + firebaseUser.getProviderData().get(firebaseUser.getProviderData().size() - 1).getProviderId());
}
You can always check the list of providers as Malik pointed out. However, as you can have multiple providers linked to the same user, to get the sign in method of the current User with multiple providers, you have to check the ID token. You need to check firebase.sign_in_provider claim in the token. That will give you the sign in method used to get the ID token. To get it on the client, you need to getIdToken and then parse the returned JWT with some JWT parser.
You can use method getIdTokenResult() of your user object (firebase.User) to get IdTokenResult object, whose signInProvider property you can use to detect signin method of your logged in user.
Reached here for same issue, unable to find the provider that user used to login.
I am working on react-js app using react-firebaseui lib for readymade UI.
After a lil struggle, I simply analysed the "user" object returned by auth and in that we receive an array "providerData".
Without further analysis I decided to use:
const signinProvider = user.providerData[0].providerId;
In my case, we use only 2 providers google & password.
I get the user from "onAuthStateChanged" function as below:
import { fbAuth } from "./firebase";
fbAuth.onAuthStateChanged(function (user) {
if (user) {
console.log("authStateChanged:=====", user);
useItFurther(user);
} else {
console.log("authStateChanged: no user logged in.");
cleanUpAuthDetailsIfApplicable();
}
});
CAUTION: I haven't researched why is providerData an array and what more can be there in that array, what could be the sequence when there are more than 1 objects in that array, etc.
In my case, we had to add a condition for email validation based on provider. Like, for a particular domain email address, force user to use a specific provider.
I have been puzzled over this problem and couldnt find an appropriate solution for a long time as well. The solution turns out to be short:
String strProvider = FirebaseAuth.getInstance().
getAccessToken(false).getResult().getSignInProvider();
So, if (strProvider.equals("password")) then the authentication is by Email + Password,
if (strProvider.equals("google.com")) then the authentication is via Google,
if (strProvider.equals("facebook.com")) then the authentication is via Facebook.
Addition
However, with this one-liner you can get an exception wchich can be prevented by adding OnSuccessListener like so:
mAuth = FirebaseAuth.getInstance();
mAuth.getAccessToken(false).addOnSuccessListener(new OnSuccessListener<GetTokenResult>() {
#Override
public void onSuccess(GetTokenResult getTokenResult) {
strProvider = getTokenResult.getSignInProvider();
}
});
Alternative
The getProviders() method list is sorted by the most recent provider
used to sign in. So the first element in getProviderData() is the
method that the user used to sign in.
Also the reason why
FirebaseAuth.getInstance().getCurrentUser().getProviderId() returns
firebase is because a backing Firebase Account is always created
irrespective of how the user signs in. This helps with linking and
unlinking of accounts where users may want to attach more than one
credential (however your view of the FirebaseUser has not changed).
as mention this post.

Can I use Firebase remote config for updating strings.xml of Android app?

Well known that Firebase Remote Config is a cloud service that lets you change the behavior and appearance of your app without requiring users to download an app update.
I was wondering that can I use Firebase Remote Config for updating strings.xml?
Sometime I need to update file strings.xml (for example: correct the translation of locale languages). And to do this I have to update my app.
It's great if we can store the strings in server like Firebase.
I think you cannot do it through Firebase, just need to update apk.
Dynamically updating strings.xml is not possible but there's a solution to this. I ran into the same problem when I wanted to do A/B testing with Firebase Remote Config. So I created FString library for android and it provides a smart solution. It currently powers Mouve Android App on production.
Installation
In your project's module build.gradle add this dependency
implementation 'com.ravindrabarthwal.fstring:fstring:1.0.0'
In your Android Application class add the following code
import com.example.myapp.R.string as RString // Import your string
class MyApp: Application() {
override fun onCreate() {
super.onCreate()
FString.init(RString::class.java) // Initializes the FString
}
}
Usage
To access the strings
// This is how you get strings without FString
context.getString(R.string.my_app_name) // This is how you normally do
// To get the string using FString use
FString.getString(context, R.string.my_app_name)
// If you prefer extension function use
context.getFString(R.string.my_app_name) // This is how FString works ;)
To update the FString values
/*
* Assume this JSON is coming from server. The JSON string
* must be parseable to a JSON object with key and value pairs
*/
var jsonFromServer = """
{
"my_app_name": "MyApp v2.0",
"some_other_string": "this is so cool",
}
""".trimIndent()
// This will update the SharedPreference using keys from
// above JSON and corresponding value. The SharedPreference will
// not save any key that is not is strings.xml
FString.update(context, jsonFromServer)
Check the library documentation for more info. If you find the answer helpful upvote this and mark it as the answer.

Categories

Resources