i am investigating Google Identity Services in my current Android project for Sign In With Google.
theres this code in the docs:-
private static final int REQUEST_CODE_GOOGLE_SIGN_IN = 1; /* unique request id */
private void signIn() {
GetSignInIntentRequest request =
GetSignInIntentRequest.builder()
.setServerClientId(getString(R.string.server_client_id))
.build();
Identity.getSignInClient(activity)
.getSignInIntent(request)
.addOnSuccessListener(
result -> {
try {
startIntentSenderForResult(
result.getIntentSender(),
REQUEST_CODE_GOOGLE_SIGN_IN,
/* fillInIntent= */ null,
/* flagsMask= */ 0,
/* flagsValue= */ 0,
/* extraFlags= */ 0,
/* options= */ null);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Google Sign-in failed");
}
})
.addOnFailureListener(
e -> {
Log.e(TAG, "Google Sign-in failed", e);
});
}
however startIntentSenderForResult method is marked ad deprecated with the following comment:-
This method has been deprecated in favour of using the Activity Result API which brings increased type
safety via an ActivityResultContract and the prebuilt contracts for common intents available in
androidx.activity.result.contract.ActivityResultContracts, provides hooks for testing, and allow
receiving results in separate, testable classes independent from your activity.
Use registerForActivityResult(ActivityResultContract, ActivityResultCallback) passing
in a StartIntentSenderForResult object for the ActivityResultContract.
i do not understand how to replace startIntentSenderForResult with registerForActivityResult(ActivityResultContract, ActivityResultCallback), when i follow the instructions and pass the StartIntentSenderForResult object for the ActivityResultContract as follows i get a compile error message
heres my code
private fun signIn() {
val request: GetSignInIntentRequest = GetSignInIntentRequest.builder()
.setServerClientId(getString(R.string.server_client_id))
.build()
Identity.getSignInClient(this#MainActivity)
.getSignInIntent(request)
.addOnSuccessListener {
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
Log.d(TAG, "signIn() called ${it.data}")
Log.d(TAG, "signIn() called ${it.resultCode}")
}
}
.addOnFailureListener { e -> Log.e(TAG, "Google Sign-in failed", e) }
}
this is what i ended up with:-
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initializeGoogleSignIn()
}
private val launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult(), ::handleSignInResult)
private fun handleSignInResult(result: ActivityResult) {
val task: Task<GoogleSignInAccount> = GoogleSignIn.getSignedInAccountFromIntent(result.data)
try {
val account = task.getResult(ApiException::class.java)
Log.d(TAG, "handleSignInResult() called with: result = $account")
} catch (ex: ApiException) {
Log.e(TAG, "handleSignInResult: ", ex)
}
}
private fun initializeGoogleSignIn() {
val request = GetSignInIntentRequest.builder()
.setServerClientId(getString(R.string.server_client_id))
.build()
Identity.getSignInClient(this)
.getSignInIntent(request)
.addOnSuccessListener { result ->
val intentSenderRequest = IntentSenderRequest.Builder(result).build()
launcher.launch(intentSenderRequest)
}
.addOnFailureListener { e ->
Log.e(TAG, "initializeGoogleSignIn: ",e )
}
}
}
Related
I am justing trying to get phone number using GetPhoneNumberHintIntentRequest to replace HintRequest.
So just trying to follow google developer doc https://developers.google.com/identity/phone-number-hint/android#kotlin_2. But after following doc I feel this doc is incomplete.
val phoneNumberHintIntentResultLauncher: ActivityResultLauncher<Intent> =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
try {
val phoneNumber = Identity.getSignInClient(requireActivity()).getPhoneNumberFromIntent(result.data)
} catch(e: Exception) {
}
}
So as per doc you need to pass intent to phoneNumberHintIntentResultLauncher but there is no method inside GetPhoneNumberHintIntentRequest.
Even if you see doc then you realise that you need to replace signInClient to getSignInClient.
If any one know about above issue then let me know or any doc where I can achieve my goal.
Have been facing this recently.
Please change the result launcher type as follows.
val resultLauncher: ActivityResultLauncher<IntentSenderRequest> = registerForActivityResult(StartIntentSenderForResult()) { result ->
try {
val phoneNumber = Identity.getSignInClient(requireActivity()).getPhoneNumberFromIntent(result.data)
// Do something with the number
} catch (e: Exception) {
Log.e(TAG, "Phone Number Hint failed")
}
And launch the intent as
...
.addOnSuccessListener { request: PendingIntent ->
try {
resultLauncher.launch(IntentSenderRequest.Builder(request).build())
} catch(e: Exception) {
Log.e(TAG, "Launching the PendingIntent failed")
}
}
...
The document is indeed incomplete as it seems.
// initialise above onCreate
private ActivityResultLauncher<IntentSenderRequest> someActivityResultLauncher;
// declare in onCreate
private void phoneSelection() {
GetPhoneNumberHintIntentRequest request = GetPhoneNumberHintIntentRequest.builder().build();
Identity.getSignInClient(RegistrationActivity.this)
.getPhoneNumberHintIntent(request)
.addOnFailureListener(e -> {
Toast.makeText(activity, "Error : "+e.getMessage(), Toast.LENGTH_SHORT).show();
}).addOnSuccessListener(pendingIntent -> {
IntentSenderRequest intentSenderRequest = new IntentSenderRequest.Builder(pendingIntent.getIntentSender()).build();
someActivityResultLauncher.launch(intentSenderRequest);
});
}
// declare in onCreate
private void resultLauncher() {
someActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartIntentSenderForResult(), result -> {
try {
String phoneNumber = Identity.getSignInClient(RegistrationActivity.this).getPhoneNumberFromIntent(result.getData());
binding.editNumber.setText(phoneNumber.substring(3)); //get the selected phone
} catch (ApiException e) {
e.printStackTrace();
Toast.makeText(activity, "Error : "+ e.getMessage(), Toast.LENGTH_SHORT).show();
}
});
}
I have the following setup
Service
// ItunesService
suspend fun searchItunesPodcast(#Query("term") term: String): Response<PodcastResponse>
Repository
// ItunesRepo
override suspend fun searchByTerm(term: String) = withContext(ioDispatcher) {
return#withContext itunesService.searchItunesPodcast(term)
}
ViewModel
fun searchPodcasts(term: String) {
viewModelScope.launch {
_res.value = Result.loading()
try {
val response = itunesRepo.searchByTerm(term)
if (response.isSuccessful) { // Nothing from here when no internet
_res.value = Result.success(response.body())
} else {
_res.value = Result.error(response.errorBody().toString())
}
} catch (e: Exception) {
_res.value = Result.exception(e)
}
}
}
Everything works great until i turn off mobile data/internet on my testing device. _res value stuck on Loading state. I have tried adding break point at if (response.isSuccessful) when there is no internet and it seams like val response = itunesRepo.searchByTerm(term) never returns how can I fix this
I switched to using Flow api on my Repository
override suspend fun searchPodcasts(term: String) = flow {
emit(Result.Loading)
try {
val res = itunesService.searchItunesPodcast(term)
if (res.isSuccessful)
emit(Result.Success(res.body()))
else
emit(Result.Error("Generic error: ${res.code()}"))
} catch (e: Exception) {
emit(Result.Error("Unexpected error", e))
}
}.flowOn(ioDispatcher)
Then collect the results on my ViewModels
I am try to cancel to api request if user calls api to fast then only the latest api should return the result all previous requests should be discarded but this isn't working anyone knows the solution please help thanks
class CartViewModel(val store: Account) : BaseViewModel() {
private var requestCalculation: Job? = null
fun recalculate() {
requestCalculation.let {
if (it != null) {
if (it.isActive) {
requestCalculation!!.cancel()
}
}
}
requestCalculation = viewModelScope.launch(Dispatchers.IO) {
isLoading.postValue(true)
try {
val order = CCOrderManager.shared.calculateTaxesAndApplyRewards(store.id)
refreshOrder()
} catch (e: Exception) {
exception.postValue(e.localizedMessage ?: e.toString())
}
}
}
}
The order of cancellation and execution is wrong. When the function starts, requestCalculation is null, so it cannot be canceled. Make sure you start first the coroutine and cancel it later. For example:
private var requestCalculation: Job? = null
fun recalculate() {
requestCalculation = viewModelScope.launch(Dispatchers.IO) {
delay(10_000)
// do your work...
}
// now the job can be canceled
requestCalculation?.cancel()
}
Adding a check after api call this.isActive {return#launch} finally worked for me...
fun recalculate() {
calculationRequest?.cancel()
isLoading.postValue(true)
calculationRequest = viewModelScope.launch(Dispatchers.IO) {
try {
val order =
CCOrderManager.shared.calculateTaxesAndApplyRewards(store.id)
// this check is the solution *******
if (!this.isActive) {return#launch}
val catalog = CatalogManager.shared().catalog
} catch (e: Exception) {
}
}
}
I have written a code to fetch data from Cloud Firestore and am trying to implement the network calls using coroutines. I have tried to follow the official guides as much as possible, but since the functions have been left incomplete in those docs, I have made adjustments according to my requirements, but those might be the problem itself.
Here's the function which fetches the data:
suspend fun fetchHubList(): MutableLiveData<ArrayList<HubModel>> = withContext(Dispatchers.IO) {
val hubList = ArrayList<HubModel>()
val liveHubData = MutableLiveData<ArrayList<HubModel>>()
hubsListCollection.get().addOnSuccessListener { collection ->
if (collection != null) {
Log.d(TAG, "Data fetch successful!")
for (document in collection) {
Log.d(TAG, "the document id is ")
hubList.add(document.toObject(HubModel::class.java))
}
} else {
Log.d(TAG, "No such document")
}
}.addOnFailureListener { exception ->
Log.d(TAG, "get failed with ", exception)
}
if (hubList.isEmpty()) {
Log.d(TAG, "Collection size 0")
} else {
Log.d(TAG, "Collection size not 0")
}
liveHubData.postValue(hubList)
return#withContext liveHubData
}
And here is the ViewModel class which is calling this method:
class HubListViewModel(application: Application): AndroidViewModel(application) {
// The data which will be observed
var hubList = MutableLiveData<ArrayList<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch (Dispatchers.IO){
hubList = hubListDao.fetchHubList()
Log.d(TAG, "Array List fetched")
}
}
}
Using the tag messages I know that an empty list is being returned, which I know from another question of mine, is because the returned ArrayList is not in sync with the fetching operation, but I don't know why, since I've wrapped the whole function inside a with context block. Please tell me why the return and fetching is not being performed sequentially.
you should add this dependency "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.3". It allows you to use await() to replace callbacks.
suspend fun fetchHubList(): List<HubModel>? = try {
hubsListCollection.get().await().map { document ->
Log.d(TAG, "the document id is ${document.id}")
document.toObject(HubModel::class.java)
}.apply {
Log.d(TAG, "Data fetch successful!")
Log.d(TAG, "Collection size is $size")
}
} catch (e: Exception) {
Log.d(TAG, "get failed with ", e)
null
}
Dispatchers.IO is not necessary since firebase APIs are main-safe
class HubListViewModel(application: Application): AndroidViewModel(application) {
val hubList = MutableLiveData<List<HubModel>>()
private val hubListDao = HubListDao()
init {
viewModelScope.launch {
hubList.value = hubListDao.fetchHubList()
Log.d(TAG, "List fetched")
}
}
}
Razorpay callbacks is not working in fragment instead of activity using fragment please give a solution If anyone aware thanks in advance.
private fun startPayment() {
val activity: Activity = requireActivity()
val co = Checkout()
try {
val options = JSONObject()
options.put("name", "Vendor")
options.put("description", " for Order")
//You can omit the image option to fetch the image from dashboard
options.put("image", "https://rzp-mobile.s3.amazonaws.com/images/rzp.png")
options.put("currency", "INR")
val payment: String = "1"//getcart?.CartTotal.toString()
// amount is in paise so please multiple it by 100
//Payment failed Invalid amount (should be passed in integer paise. Minimum value is 100 paise, i.e. ₹ 1)
var total = payment.toDouble()
total = total * 100
options.put("amount", total)
val preFill = JSONObject()
preFill.put("email", "hell#gmail.com")
preFill.put("contact", "9898989898")
options.put("prefill", preFill)
co.open(requireActivity(), options)
} catch (e: Exception) {
Toast.makeText(activity, "Error in payment: " + e.message, Toast.LENGTH_SHORT).show()
e.printStackTrace()
}
}
override fun onPaymentSuccess(s: String?) {
toast("onPaymentSuccess")
Log.i(TAG, "onPaymentSuccess: $s")
}
override fun onPaymentError(i: Int, s: String?) {
Log.e(TAG, "error code "+i.toString()+" -- Payment failed "+s.toString())
try {
toast("Payment error please try again")
} catch (e : Exception) {
Log.e("OnPaymentError", "Exception in onPaymentError", e);
}
}
Amount should be in Integer and in paise.
implement result listener to the fragment host activity and override error and success function there.
in fragment you won't get Razorpay callback function, so you should implement PaymentResultListener or PaymentResultWithDataListener in activity and from activity you have call your fragment and do your api call for razor pay response.
in your Activity:
#Override
public void onPaymentSuccess(String s, PaymentData paymentData) {
try {
FeeFragment feeList = (FeeFragment) mViewPager.getAdapter().instantiateItem(mViewPager, mViewPager.getCurrentItem());
feeList.checkRazorResponse(paymentData, true);
} catch (Exception e) {
Log.e("Exception in success", e.toString());
e.printStackTrace();
}
}
in your Fragment:
public void checkRazorResponse(PaymentData paymentData, boolean success) {
if (success) {
updatePaymentStatus(paymentData);
//do your api call
} else {
//handle error message
}
}
Razorpay payment integration is not supported on the fragment. You have to implement it on Activity.
Like this way
This code is for fragment:
binding.tvPlaceOrder.setOnClickListener(view -> {
startActivity(new Intent(getActivity(), PaymentActivity.class));
}
}
});
This code is for Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPaymentBinding.inflate(layoutInflater)
setContentView(binding.root)
payOnline()
}
private fun payOnline() {
val checkout = Checkout()
checkout.setKeyID(getString(R.string.razorpay_api_key))
checkout.setImage(R.mipmap.ic_launcher)
try {
val options = JSONObject()
options.put("name", "mName")
options.put("currency", "INR")
options.put("image", R.mipmap.ic_launcher)
options.put("amount", 10000) //pass amount in currency subunits
options.put("prefill.email", "roydeveloper01#gmail.com")
options.put("prefill.contact", "8620828385")
checkout.open(this, options)
} catch (e: Exception) {
Toast.makeText(this, "Error in starting Razorpay Checkout: $e", Toast.LENGTH_LONG).show()
}
}
override fun onPaymentSuccess(p0: String?) {
try {
binding.tvId.text = "Payment Successful \n Transaction ID $p0"
} catch (e: NoSuchAlgorithmException) {
binding.tvId.text ="Exception"
}
}
override fun onPaymentError(p0: Int, p1: String?) {
try {
binding.tvId.text ="Exception: $p1"
} catch (e: NoSuchAlgorithmException) {
e.printStackTrace()
}
binding.tvId.text ="Error: $p1"
}
My problem is solved this way. I think your problem will also solve . Best of luck.