Google authentication with Firebase and Jetpack Compose - firebase

I'm using Kotlin, Android Studio and Jetpack Compose
What I want to do:
After the button click, I would want to register the user using google authentication and firebase
What I've tried to do:
I've tried to follow Firebase Auth Documentation, but it's really hard to understand as it often sends me back to the Google Documentation, which isn't helpful either in the case of Jetpack Compose.
I couldn't find any up to date videos or guides explaining this implementation. It would be awesome if someone could explain how to start with it.

val context = LocalContext.current
val token = stringResource(R.string.default_web_client_id)
val launcherNav = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
navController.navigate(Screen.MainScreen.route)
}
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult(),
) {
val task =
try {
val account = GoogleSignIn.getSignedInAccountFromIntent(it.data)
.getResult(ApiException::class.java)
val credential = GoogleAuthProvider.getCredential(account.idToken!!, null)
FirebaseAuth.getInstance().signInWithCredential(credential)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
}
}
}
catch (e: ApiException) {
Log.w("TAG", "GoogleSign in Failed", e)
}
}
Button(
onClick = {
val gso = GoogleSignInOptions
.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(token)
.requestEmail()
.build()
val googleSignInClient = GoogleSignIn.getClient(context, gso)
launcher.launch(googleSignInClient.signInIntent)
}
) {
Text(text = "Sign In")
}

Related

The sms code from firebase does not match

I created a number sending activity and confirmation/otp fragment using firebase phone auth. When directed to the confirmation page, a 6-digit sms code from Firebase is sent to the phone number entered, but no matter what I do, the entered edittext and the codes from firebase do not match.
When I leave the edit text blank, it redirects to the fragment I want as if it were correct. Can you help me where am I making a mistake? My codes in the confirmation fragment are as follows;
class FragmentRegisterTelOnay : Fragment() {
var comingNumber = ""
lateinit var auth : FirebaseAuth
lateinit var callbacks : PhoneAuthProvider.OnVerificationStateChangedCallbacks
var verificationID = ""
var comingCode : String = ""
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {
var view = inflater.inflate(R.layout.fragment_register_activity_phone,container,false)
view.tvKullaniciTelNo.setText("+90"+comingNumber)
auth = Firebase.auth
setupCallBack()
view.ileriButton.setOnClickListener {
if (comingCode.equals(editTextOnayKodu.text.toString())){
EventBus.getDefault().postSticky(EventBusDataEvents.KayitBilgileriniGonder("+90$comingNumber",null,verificationID,comingCode))
val transaction = requireActivity().supportFragmentManager.beginTransaction()
transaction.replace(R.id.telefonOnayKod,FragmentRegisterDetailPhone())
transaction.addToBackStack("TelOnayfragmentEklendi")
transaction.commit()}
else{
Toast.makeText(activity,"Wrong Code",Toast.LENGTH_LONG).show()
}
}
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber("+90"+comingNumber) // Phone number to verify
.setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit
.setActivity(requireActivity()) // Activity (for callback binding)
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
return view
}
private fun setupCallBack() {
callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
if(!credential.smsCode.isNullOrEmpty()){
comingCode = credential.smsCode!!
progressBarOnayKod.visibility = View.GONE
Log.e("Success","on verificationcompleted sms: " + comingCode)}
else{
Log.e("Error","onverification has not completed")
}
}
override fun onVerificationFailed(e: FirebaseException) {
Log.e("Error: ",e.localizedMessage)
progressBarOnayKod.visibility = View.GONE
}
override fun onCodeSent(verificationId: String,token: PhoneAuthProvider.ForceResendingToken) {
verificationID = verificationId
progressBarOnayKod.visibility = View.VISIBLE
Log.e("Codesent","oncodesent worked")
}
}
}
#Subscribe (sticky = true)
internal fun onTelefonEvent(kayitBilgileri: EventBusDataEvents.KayitBilgileriniGonder){
comingNumber = kayitBilgileri.telNo.toString()
Log.e("test",comingNumber)
}
override fun onAttach(context: Context) {
super.onAttach(context)
EventBus.getDefault().register(this)
}
override fun onDetach() {
super.onDetach()
EventBus.getDefault().unregister(this)
}
}
first set the sha1 to firebase setting and generate google config.json then add to poject's root directory and add to build.gradle dependency.
it'll work properly
resources: https://github.com/firebase/quickstart-android/issues/283

How to keep user logged in when using FB plugin to sign in?

I am trying to find out how to keep user logged in. I am using Facebook Plugin and storing the user data in CosmosDB, however i am not sure what data do i need to keep the user logged in or when to actually ask his permission as i am redirected straight to Facebook login.
This is the code that i am using to sign in
async Task LoginFacebookAsync(User user)
{
try
{
if (_facebookService.IsLoggedIn)
{
_facebookService.Logout();
}
EventHandler<FBEventArgs<string>> userDataDelegate = null;
userDataDelegate = async (object sender, FBEventArgs<string> e) =>
{
if (e == null) return;
switch (e.Status)
{
case FacebookActionStatus.Completed:
var facebookProfile = await Task.Run(() => JsonConvert.DeserializeObject<FacebookProfile>(e.Data));
var socialLoginData = new User
{
UserEmail = facebookProfile.Email,
UserName = $"{facebookProfile.FirstName} {facebookProfile.LastName}",
Id = facebookProfile.UserId,
};
user.UserEmail = socialLoginData.UserEmail;
user.UserName = socialLoginData.UserName;
user.Id = socialLoginData.Id;
user = await UserViewModel.GetOrCreateUser(user);
UserViewModel.SetUser(user);
await App.Current.SavePropertiesAsync();
App.Current.MainPage = new AppShell();
break;
case FacebookActionStatus.Canceled:
break;
}
_facebookService.OnUserData -= userDataDelegate;
};
_facebookService.OnUserData += userDataDelegate;
string[] fbRequestFields = { "email", "first_name", "gender", "last_name" };
string[] fbPermisions = { "email" };
await _facebookService.RequestUserDataAsync(fbRequestFields, fbPermisions);
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
}
Please if you could help how to approach this.
You may use local db like Sqlite or Settings Plugin to save the authentication token, expire date etc. So when the app run you can check them and let the user automatically login or not.

Firebase UI Email Link signin intent extra is null

The following 3 functions are used to set up email and other auth checks (called in order)
private void buildSignInIntentBuilder() {
ActionCodeSettings actionCodeSettings = ActionCodeSettings.newBuilder()
.setAndroidPackageName(getString(R.string.packageName), true, null)
.setHandleCodeInApp(true)
.setUrl(getString(R.string.dynamic_link_url))
.build();
List<AuthUI.IdpConfig> providers = Arrays.asList(
new AuthUI.IdpConfig.EmailBuilder()
.enableEmailLinkSignIn()
.setActionCodeSettings(actionCodeSettings)
.build(),
// new AuthUI.IdpConfig.EmailBuilder().setRequireName(false).build(),
new AuthUI.IdpConfig.PhoneBuilder()
.build(),
new AuthUI.IdpConfig.GoogleBuilder()
.build(),
new AuthUI.IdpConfig.FacebookBuilder()
.build());
signInIntentBuilder = AuthUI.getInstance()
.createSignInIntentBuilder()
.setIsSmartLockEnabled(false)
.setAvailableProviders(providers)
.setLogo(R.drawable.icon_forget_me_not_1);
}
private void catchEmailLinkSignIn() {
Log.d(TAG, "Intent: " + getIntent().getExtras());
if (AuthUI.canHandleIntent(getIntent())) {
if (getIntent().getExtras() == null) {
return;
}
String link = getIntent().getExtras().getString(ExtraConstants.EMAIL_LINK_SIGN_IN);
Log.d(TAG, "link: " + link);
if (link != null) {
signInIntentBuilder.setEmailLink(link);
}
}
}
private void createCheckAndSigninListener() {
// set firebase sign in listener
mAuthStateListner = firebaseAuth -> {
// Already logged in
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
Log.d(TAG, "user already signed in");
// Check user even if signed in to register him to database (if haven't)
FirebaseAuthHelper.getInstance().checkRegisterUser(user, this, CHECK_USER_DB);
} else {
Log.d(TAG, "user hasn't signed in");
// Signed out or hasn't logged in
startActivityForResult(
signInIntentBuilder
.build(),
RC_SIGN_IN
);
}
};
}
I have set up a dynamic link with firebase hosting. And being able to redirect into the same activity upon clicking the received email link.
However,
String link = getIntent().getExtras().getString(ExtraConstants.EMAIL_LINK_SIGN_IN);
Log.d(TAG, "link: " + link); // --> produces "link: null"
Shows despite successfully getting a intent, there is no EMAIL_LINK_SIGNIN extra in the getExtras(). I spend few hours looking into the source code of FirebaseUi, but I didn't find where the constant EMAIL_LINK_SIGN_IN is used and how is the intent from dynamic link parsed.
Any idea how to fix this problem is appreciated. I had already spent a whole day trying to figure this out.
Instead of using String link = getIntent().getExtras().getString(ExtraConstants.EMAIL_LINK_SIGN_IN);
use getIntent().getData().toString(); instead.

How to access already existing data in Realm cloud using Xamarin Forms?

I’m using Realm for my Xamarin forms App.I have synced my data in Sql server with Realm cloud.Now I want to view the data which is there in Realm cloud in my Xamarin Forms App.
I used code
public ListsViewModel()
{
LogoutCommand = new Command(Logout);
AddressCommand = new Command(AddList);
//TaskLists = new IQueryable<Address>();
IQueryable<Address> TaskLists = Enumerable.Empty<Address>().AsQueryable();
//AddList();
}
private void AddList()
{
_realm = Realm.GetInstance();
TaskLists = _realm.All<Address>();
TaskLists.Count();
}
My TaskLists.Count() gives 0.But my ROS has the data from sql server and they both are in sync.And I'm able to Login to my Realm Object Server through my Xamarin Forms App.But my Xamarin Forms app is not syncing with my ROS that is the data which is there in my Realm object server is not displaying in my APP.I want to display the data of Address class in my App.The data of address class is put through SQL server.I just Have to retrieve the data of Address class. I even tried using SyncConfiguration.
private async void AddList()
{
User user = null;
try
{
user = User.Current;
}
catch (Exception ex)
{
HandleException(ex);
}
if (user == null)
{
try
{
user = await NavigationService.Prompt<LoginViewModel, User>();
}
catch (Exception ex)
{
HandleException(ex);
}
}
else
{
var uri = user.ServerUri;
Constants.Server.SyncHost = $"{uri.Host}:{uri.Port}";
}
var config = new SyncConfiguration(user, Constants.Server.SyncServerUri)
{
ObjectClasses = new[] { typeof(Address) }
};
_realm = Realm.GetInstance(config);
//_realm = Realm.GetInstance();
TaskLists = _realm.All<Address>();
_realm.Write(() =>
{
_realm.Add(new Address { ID = 8, ZipCode = "Judson123" });
});
TaskLists.Count();
}
Still I’m not able to fix it.Please Help me with this.

Instert data when you receive a push notification from GCM and your app was not runing

I receive correctly a notification from GCM but I want insert the message in a local (sqlite) database.
If I receive the notification when my app is not running, it doesn't insert the message but if my application was running then it does.
void SendNotification (string message)
{
var intent = new Intent (this, typeof(MainActivity));
intent.AddFlags (ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity (this, 0, intent, PendingIntentFlags.OneShot);
var notificationBuilder = new Notification.Builder (this)
.SetSmallIcon (Resource.Drawable.icstatbutton_click)
.SetContentTitle ("GCM Message")
.SetContentText ("U heeft een nieuwe vragenlijst.")
.SetAutoCancel (true)
.SetContentIntent (pendingIntent);
var notificationManager = (NotificationManager)GetSystemService(Context.NotificationService);
notificationManager.Notify (0, notificationBuilder.Build());
try
{
DataAccess.InsertDownload (message);
}
catch (Exception ex)
{
}
}
Can I access sqlite database when my application is not running ?
Is your DataAccess.InsertDownload() method in your shared code? If so, that is the same thing I ran into.
Probably not the best way to solve it but what I did was to save the JSON string into Android's SharedPreferences if the app is in fact closed. Then the app is loaded again, within MainActivity and after loading the shared project, I attempt to read out any SharedPreferences and save them to the DB.
Below is some code showing this. Here is a link to SettingsImplementation.
public async Task SaveNotifToDb(Notification notification) {
try {
DataAccess.InsertDownload (message);
} catch(System.InvalidOperationException) { //InvalidOperationException is the exception given when your shared code is not yet loaded, meaning the app is closed, so now lets save to Preferences
System.Console.WriteLine("\nIn APP.Droid.Helpers.GcmService.SaveNotificationAsync() - InvalidOperationException, the app is probably closed. Saving to Shared Preferences\n");
string notificationJson = Newtonsoft.Json.JsonConvert.SerializeObject(notification);
string emptyCheck = SettingsImplementation.GetValueOrDefault<string>(DroidConstants.NotificationSettingKeyPart + "0", DroidConstants.NotificationSettingDefault);
if(emptyCheck.Length > 0) {
int index = 0;
while(emptyCheck.Length > 0) {
emptyCheck = SettingsImplementation.GetValueOrDefault<string>(DroidConstants.NotificationSettingKeyPart + index.ToString(), DroidConstants.NotificationSettingDefault);
index ++;
}
SettingsImplementation.AddOrUpdateValue<string>(DroidConstants.NotificationSettingKeyPart + (index - 1).ToString(), notificationJson);
} else { SettingsImplementation.AddOrUpdateValue<string>(DroidConstants.NotificationSettingKeyPart + "0", notificationJson); }
return notification;
}
}
Now when the app is started, we wait for the shared code to load and then try to read all the notification JSON back out.
MainActivity.OnCreate():
base.OnCreate(bundle);
Xamarin.Forms.Forms.Init(this, bundle);
string notificationJson = SettingsImplementation.GetValueOrDefault(DroidConstants.NotificationSettingKeyPart + "0", DroidConstants.NotificationSettingDefault); //Check to see if we have a saved notification
if(notificationJson.Length > 0) {
int index = 0;
while(notificationJson.Length > 0) { //Keep trying until no more notifications can be gatherd
notificationJson = SettingsImplementation.GetValueOrDefault(DroidConstants.NotificationSettingKeyPart + index, DroidConstants.NotificationSettingDefault);
if(notificationJson.Length > 0) {
Data.Models.RemoteNotification notification = Newtonsoft.Json.JsonConvert.DeserializeObject<Data.Models.RemoteNotification>(notificationJson);
if(notification != null) {
try {
await App.RemoteNotificationRepo.InsertAsync(notification);
} catch(System.Exception e) {
System.Console.WriteLine("\nIn APP.Droid.MainActivity.OnCreate() - Exception attempting to create new in app notification\n{0}\n", e);
}
}
SettingsImplementation.Remove(DroidConstants.NotificationSettingKeyPart + index.ToString());
index++;
}
}
}
Yes. You can access Sqlite when application is not running. In your Activity's OnCreate you can check for new messages and update accordingly.

Resources