i'm actually new to Kotlin android development. I'm making an app that uses Google sheets as a database. My app can successfully run after Google sign in. In fact I want my user to sign in to app, if their email ID is present in the Emails sheet in the Google sheet. So I have done following steps in my code so far.
Sign in user with Google Sign In
Retrieve data from "Emails" sheet in my Google Spreadsheet
Then store them in to a data class (Customers data class in Shipment.kt)
Then I check whether signed in user's email ID available in the data class or not. This is where I need help. It's giving me this error
"Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly."
Can someone help me on this?
Below is my data class in Shipment.kt
package com.example.demoonlinesheet
data class Shipments(
val shipperName:String,
val volume:String,
val eta:String,
val etd:String)
data class Customers(
val companyName:String,
val customerName:String,
val emailID:String)
Below is the code that I have written so far in MainActivity.kt
const val RC_SIGN_IN = 123
const val EXTRA_MESSAGE = "com.example.demoonlinesheet.MESSAGE"
const val EXTRA_MESSAGE2 = "com.example.demoonlinesheet.MESSAGE"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Configure sign-in to request the user's ID, email address, and basic
// profile. ID and basic profile are included in DEFAULT_SIGN_IN.
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.build()
// Build a GoogleSignInClient with the options specified by gso.
var mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
sign_in_button.setOnClickListener{
val signInIntent = mGoogleSignInClient.signInIntent
startActivityForResult(signInIntent, RC_SIGN_IN)
}
val acct = GoogleSignIn.getLastSignedInAccount(this)
if (acct != null) {
startActivity(Intent(this, SecondActivity::class.java).apply{
putExtra(EXTRA_MESSAGE, acct.displayName)
//putExtra(EXTRA_MESSAGE2, acct.email)
} )
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// Result returned from launching the Intent from GoogleSignInClient.getSignInIntent(...);
if (requestCode == RC_SIGN_IN) {
// The Task returned from this call is always completed, no need to attach
// a listener.
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
handleSignInResult(task)
}
}
private fun handleSignInResult(completedTask: Task<GoogleSignInAccount>) {
val companyList= arrayListOf<Customers>()
val url="https://sheets.googleapis.com/v4/spreadsheets/{sheetID}/values/{sheetName}?alt=json&key={APIKey}"
val queue = Volley.newRequestQueue(this)
val jsonObjectRequest = JsonObjectRequest(Request.Method.GET, url, null,
{
fun onResponse(response: JSONObject) {
try {
// val feedObj = response.getJSONObject("")
val entryArray = response.getJSONArray("values")
for (i in 2 until entryArray.length()) {
val entryObj = entryArray.getJSONArray(i)
val companyName = entryObj[0].toString()
val customerName = entryObj[1].toString()
val emailID = entryObj[2].toString()
// entryObj.getJSONObject("gsx\$lastname").getString("\$t")
companyList.add(Customers(companyName, customerName, emailID))
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
}, {
fun onErrorResponse(error: VolleyError?) {
Toast.makeText(this#MainActivity, "Fail to get data..", Toast.LENGTH_SHORT)
.show()
}
})
queue.add(jsonObjectRequest)
fun checkUser() {
val getAccount = completedTask.getResult(ApiException::class.java)
val emailLoggedIn = getAccount.email.toString()
val companies = listOf<Customers>()
//this is where I get the error message "Type inference failed. The value of the type parameter T should be mentioned in input types (argument types, receiver type or expected type). Try to specify it explicitly."
if (emailLoggedIn in companies){
//do something here
}
try {
val account = completedTask.getResult(ApiException::class.java)
val loggedname = account.displayName
//startActivity(Intent(this#MainActivity, SecondActivity::class.java))
val intent = Intent(this, SecondActivity::class.java).apply {
putExtra(EXTRA_MESSAGE, loggedname)
}
startActivity(intent)
} catch (e: ApiException) {
}
}
}
From your code:
if (emailLoggedIn in companies) {
//do something here
}
emailLoggedIn is a String, companies is a List<Customers>.
How does List know how to compare String and Customers? :)
You need something like this:
if (companies.any { it.emailID == emailLoggedIn }){
//do something here
}
I should also mention that you can leave the if condition unchanged and add the following code that overloads the in keyword:
operator fun List<Customers>.contains(email: String): Boolean {
return this.any { it.emailID == email }
}
But, in my opinion, this overload looks terrible and confusing :)
Related
Unable to extract information from the datasnapshot received from firebase.
Currently, I am able to get the dataSnapshot from firebase, but I am having problems extracting the information from it.
In the example below I have a lobby with the code "81MUB" and inside I have a list of players (only using one player in the example). Data from FireBase
{
"81MUB": [
{
"name": "Alejandro",
"points": 0
}
]
}
Data Class
data class Player(
val name: String,
val points: Int
)
Listener
fun getCode(): String {
val index = ('A'..'Z') + ('1'..'9')
var code = ""
for (i in 0..4){
code += index[Random().nextInt(index.size)]
}
return code
}
class MviewModel : ViewModel() {
private val _Players: MutableLiveData<MutableList<Player>> =
MutableLiveData(mutableListOf<Player>(Player("Alejandro", 0)))
private var _LobbyCode: String = ""
private val dataBase = FirebaseDatabase.getInstance()
fun getPlayer(): MutableLiveData<MutableList<Player>> = _Players
fun createLobby() {
_LobbyCode = getCode()
}
fun listener() {
val postListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
}
}
dataBase.reference.child(_LobbyCode).addValueEventListener(postListener)
}
}
Any tips?
Each time you call getCode() you are generating a new random code. When reading data, you always use the exact same code that exists in the database. So in code, it should look like this:
val db = Firebase.database.reference
val codeRef = db.child("81MUB")
codeRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
val name = snapshot.child("name").getValue(String::class.java)
val points = snapshot.child("points").getValue(Long::class.java)
Log.d("TAG", "$name/$points")
} else {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
The result in the logcat will be:
Alejandro/0
If you however want to map the 81MUB node into an object of type Player, then your data class should look like this:
data class Player(
val name: String? = null,
val points: Int? = null
)
And in code:
val db = Firebase.database.reference
val codeRef = db.child("81MUB")
codeRef.get().addOnCompleteListener {
if (it.isSuccessful) {
val snapshot = it.result
val player = snapshot.getValue(Player::class.java)
Log.d("TAG", "${player.name}/${player.points}")
} else {
Log.d("TAG", error.getMessage()) //Never ignore potential errors!
}
}
Which will produce the exact same output as above.
You might also take into consideration, using the DatabaseReference#push() method which:
Create a reference to an auto-generated child location. The child key is generated client-side and incorporates an estimate of the server's time for sorting purposes.
Instead of using your codes.
Did anyone implement google autocomplete suggestion text field or fragment in a jetpack compose project? If so kindly guide or share code snippets as I'm having difficulty in implementing it.
Update
Here is the intent that I'm triggering to open full-screen dialog, but when I start typing within it gets closed, and also I'm unable to figure out what the issue is and need a clue about handling on activity result for reading the result of the predictions within this compose function.
Places.initialize(context, "sa")
val fields = listOf(Place.Field.ID, Place.Field.NAME)
val intent = Autocomplete.IntentBuilder(
AutocompleteActivityMode.FULLSCREEN,fields).build(context)
startActivityForResult(context as MainActivity,intent, AUTOCOMPLETE_REQUEST_CODE, Bundle.EMPTY)
I am using the MVVM architecture and this is how I implemented it:
GooglePlacesApi
I've created an api for reaching google api named GooglePlacesApi
interface GooglePlacesApi {
#GET("maps/api/place/autocomplete/json")
suspend fun getPredictions(
#Query("key") key: String = <GOOGLE_API_KEY>,
#Query("types") types: String = "address",
#Query("input") input: String
): GooglePredictionsResponse
companion object{
const val BASE_URL = "https://maps.googleapis.com/"
}
}
The #Query("types") field is for specifiying what are you looking for in the query, you can look for establishments etc.
Types can be found here
Models
So I created 3 models for this implementation:
GooglePredictionsResponse
The way the response looks if you are doing a GET request with postman is:
Google Prediction Response
You can see that we have an object with "predictions" key so this is our first model.
data class GooglePredictionsResponse(
val predictions: ArrayList<GooglePrediction>
)
GooglePredictionTerm
data class GooglePredictionTerm(
val offset: Int,
val value: String
)
GooglePrediction
data class GooglePrediction(
val description: String,
val terms: List<GooglePredictionTerm>
)
I only needed that information, if you need anything else, feel free to modify the models or create your own.
GooglePlacesRepository
And finally we create the repository to get the information (I'm using hilt to inject my dependencies, you can ignore those annotations if not using it)
#ActivityScoped
class GooglePlacesRepository #Inject constructor(
private val api: GooglePlacesApi,
){
suspend fun getPredictions(input: String): Resource<GooglePredictionsResponse>{
val response = try {
api.getPredictions(input = input)
} catch (e: Exception) {
Log.d("Rently", "Exception: ${e}")
return Resource.Error("Failed prediction")
}
return Resource.Success(response)
}
}
Here I've used an extra class I've created to handle the response, called Resource
sealed class Resource<T>(val data: T? = null, val message: String? = null){
class Success<T>(data: T): Resource<T>(data)
class Error<T>(message: String, data:T? = null): Resource<T>(data = data, message = message)
class Loading<T>(data: T? = null): Resource<T>(data = data)
}
View Model
Again I'm using hilt so ignore annotations if not using it.
#HiltViewModel
class AddApartmentViewModel #Inject constructor(private val googleRepository: GooglePlacesRepository): ViewModel(){
val isLoading = mutableStateOf(false)
val predictions = mutableStateOf(ArrayList<GooglePrediction>())
fun getPredictions(address: String) {
viewModelScope.launch {
isLoading.value = true
val response = googleRepository.getPredictions(input = address)
when(response){
is Resource.Success -> {
predictions.value = response.data?.predictions!!
}
}
isLoading.value = false
}
}
fun onSearchAddressChange(address: String){
getPredictions(address)
}
}
If you need any further help let me know
I didn't include UI implementation because I assume it is individual but this is the easier part ;)
#Composable
fun MyComponent() {
val context = LocalContext.current
val intentLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {
when (it.resultCode) {
Activity.RESULT_OK -> {
it.data?.let {
val place = Autocomplete.getPlaceFromIntent(it)
Log.i("MAP_ACTIVITY", "Place: ${place.name}, ${place.id}")
}
}
AutocompleteActivity.RESULT_ERROR -> {
it.data?.let {
val status = Autocomplete.getStatusFromIntent(it)
Log.i("MAP_ACTIVITY", "Place: ${place.name}, ${place.id}")
}
}
Activity.RESULT_CANCELED -> {
// The user canceled the operation.
}
}
}
val launchMapInputOverlay = {
Places.initialize(context, YOUR_API_KEY)
val fields = listOf(Place.Field.ID, Place.Field.NAME)
val intent = Autocomplete
.IntentBuilder(AutocompleteActivityMode.OVERLAY, fields)
.build(context)
intentLauncher.launch(intent)
}
Column {
Button(onClick = launchMapInputOverlay) {
Text("Select Location")
}
}
}
I am using registerForActivityResult for google sign in implementation in my development. Everything was working fine until I upgraded my fragment dependency to 1.3.0-beta01. The application current crash with the error
java.lang.IllegalStateException: LifecycleOwner SignupChoicesFragment{8e0e269} (193105b9-afe2-4941-a368-266dbc433258) id=0x7f090139} is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
I have used the function before oncreate using lazy loading but it wont work still.
class SignupChoicesFragment : DaggerFragment() {
#Inject
lateinit var viewModelProviderFactory: ViewModelFactory
val userViewModel: UserViewModel by lazy {
ViewModelProvider(this, viewModelProviderFactory).get(UserViewModel::class.java)
}
#Inject
lateinit var mGoogleSignInClient:GoogleSignInClient
val arg:SignupChoicesFragmentArgs by navArgs()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_signup_choices, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
google_sign_in_button.setOnClickListener {
val intent = mGoogleSignInClient.signInIntent
val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult(), ActivityResultCallback {result->
if (result.resultCode == Activity.RESULT_OK) {
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
task.addOnCompleteListener {
if (it.isSuccessful) {
val account: GoogleSignInAccount? =
it.getResult(ApiException::class.java)
val idToken = it.result?.idToken
val email = account?.email
val lastName = account?.familyName
val firstName = account?.givenName
val otherName = account?.displayName
val imageUrl = account?.photoUrl
val category = arg.category
val newUser = User()
newUser.firstName = firstName
newUser.lastName = lastName
newUser.otherName = otherName
newUser.category = category
newUser.email = email
newUser.imageUrl = imageUrl.toString()
userViewModel.currentUser = newUser
newUser.token = idToken
i(title, "idToken $idToken")
requireActivity().gdToast("Authentication successful", Gravity.BOTTOM)
val action = SignupChoicesFragmentDirections.actionSignupChoicesFragmentToEmailSignupFragment()
action.newUser = newUser
goto(action)
} else {
requireActivity().gdToast(
"Authentication Unsuccessful",
Gravity.BOTTOM
)
Log.i(title, "Task not successful")
}
}
} else {
Log.i(title, "OKCODE ${Activity.RESULT_OK} RESULTCODE ${result.resultCode}")
}
}).launch(intent)
}
}
For me, the issue was that I was calling registerForActivityResult within an onClickListener which was only invoked on clicking a button (the app at this point is in state RESUMED). Moving the call outside the button's onClickListener and into the Activity's onCreate method fixed it.
quote from documentation
registerForActivityResult() is safe to call before your fragment or activity is created, allowing it to be used directly when declaring member variables for the returned ActivityResultLauncher instances.
Note: While it is safe to call registerForActivityResult() before your fragment or activity is created, you cannot launch the ActivityResultLauncher until the fragment or activity's Lifecycle has reached CREATED.
so to solve your issue move your register call outside the onCreate() and put it in fragment scope, and on google_sign_in_button click-listener call launch function
Note: if you are using Kotlin-Android-Extention move your click-listener call to onViewCreated()
If you are using a Fragment, please make sure that you are NOT performing the registerForActivityResult on the activity. Fragments also have a registerForActivityResult and that's the one you should use.
you must remove val launcher = registerForActivityResult... out of the setOnClickListener, then save it in a variable, in your example is launcher and in the setOnClickListener execute the variable with .launch, in your example es launcher.
your code would look like this
google_sign_in_button.setOnClickListener {
val intent = mGoogleSignInClient.signInIntent
launcher.launch(intent)
}
private val launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult(), ActivityResultCallback {result->
if (result.resultCode == Activity.RESULT_OK) {
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
task.addOnCompleteListener {
if (it.isSuccessful) {
val account: GoogleSignInAccount? =
it.getResult(ApiException::class.java)
val idToken = it.result?.idToken
val email = account?.email
val lastName = account?.familyName
val firstName = account?.givenName
val otherName = account?.displayName
val imageUrl = account?.photoUrl
val category = arg.category
val newUser = User()
newUser.firstName = firstName
newUser.lastName = lastName
newUser.otherName = otherName
newUser.category = category
newUser.email = email
newUser.imageUrl = imageUrl.toString()
userViewModel.currentUser = newUser
newUser.token = idToken
i(title, "idToken $idToken")
requireActivity().gdToast("Authentication successful", Gravity.BOTTOM)
val action = SignupChoicesFragmentDirections.actionSignupChoicesFragmentToEmailSignupFragment()
action.newUser = newUser
goto(action)
} else {
requireActivity().gdToast(
"Authentication Unsuccessful",
Gravity.BOTTOM
)
Log.i(title, "Task not successful")
}
}
} else {
Log.i(title, "OKCODE ${Activity.RESULT_OK} RESULTCODE ${result.resultCode}")
}
})
Source : https://medium.com/codex/android-runtime-permissions-using-registerforactivityresult-68c4eb3c0b61
registerForActivityResult() is safe to call before your fragment or activity is created, allowing it to be used directly when declaring member variables for the returned ActivityResultLauncher instances.
you should call registerForActivityResult before view created. member variables or onCreate()
If you are working with any third party library then it may happens that you can't see the "registerForActivityResult" in your code but it should be present in classes provided by that same library.
So in this case I will suggest to move out the lines which is related to that library from any listener to the onCreate method.
for example -
btnBackup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
final RoomBackup roomBackup = new RoomBackup(GoogleDriveActivity.this);
roomBackup.database(LocalDataBase.getInstance(getApplicationContext()));
roomBackup.enableLogDebug(true);
roomBackup.backupIsEncrypted(false);
roomBackup.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_INTERNAL);
roomBackup.onCompleteListener((success, message, exitCode) -> {
Log.d(TAG, "success: " + success + ", message: " + message + ", exitCode: " + exitCode);
if (success) roomBackup.restartApp(new Intent(getApplicationContext(), GoogleDriveActivity.class));
});
roomBackup.restore();
}
});
//// remove other code from listener and shift in onCreate
roomBackup = new RoomBackup(GoogleDriveActivity.this);
roomBackup.database(LocalDataBase.getInstance(getApplicationContext()));
roomBackup.enableLogDebug(true);
roomBackup.backupIsEncrypted(false);
roomBackup.backupLocation(RoomBackup.BACKUP_FILE_LOCATION_INTERNAL);
roomBackup.maxFileCount(5);
roomBackup.onCompleteListener((success, message, exitCode) -> {
Log.d(TAG, "success: " + success + ", message: " + message + ", exitCode: " + exitCode);
if (success) roomBackup.restartApp(new Intent(getApplicationContext(), GoogleDriveActivity.class));
});
/// you can keep only required lines in listener
btnBackup.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
roomBackup.backup();
}
});
That's it!
Found the same issue and manage to get to work with some magic.
In my case, it was happening in an Activity, so I went about it as such:
//...other bits
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
// doing the setup here
setupViews()
}
private fun setupViews() {
val buttonLauncher = navigator.gotoScreenForResult(this) { success ->
if (success) {
setResult(Activity.RESULT_OK)
finish()
}
}
binding.myButton.setOnClickListener {
buttonLauncher.launch(Unit)
}
Where the navigator.gotoScreenForResult would look like the following:
override fun gotoScreenForResult(context: AppCompatActivity, callback: (Boolean) -> Unit): ActivityResultLauncher<Unit> {
val contract = object : ActivityResultContract<Unit, Boolean>() {
override fun createIntent(context: Context, input: Unit?): Intent {
return Intent(context, MyNextActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
return resultCode == Activity.RESULT_OK
}
}
return context.registerForActivityResult(contract) { callback(it) }
}
Just make sure the setupViews is done within the onCreate and not on the resume step.
I am new to Android and Kotlin and am currently working on a centralized API router class.
To achieve this I am using the Fuel Framework.
For the doAsync function, I use the Anko for Kotlin library.
To retrieve an authorization token from the API I currently use this method:
private fun Login(username: String, password: String, callback: (Map<Boolean, String>) -> Unit) {
"/auth/token.json".httpPost()
.header(mapOf("Content-Type" to "application/json"))
.body("""{"username":"$username", "password":"$password"}""", Charsets.UTF_8)
.response { request, response, result ->
request.headers.remove("Accept-Encoding")
when (result) {
is Result.Failure -> {
// val data = result.get()
val ex = result.getException()
val serverResponseJson = response.data.toString(Charsets.UTF_8)
var exceptionMessage = ex.message
val jelement = JsonParser().parse(serverResponseJson)
val jobject = jelement.asJsonObject
val serverResponseError = if (jobject.has("Error")) jobject.get("Error").asString else jobject.get("detail").asString
callback(mapOf(Pair(false, serverResponseError)))
}
is Result.Success -> {
val data = result.get()
val returnJson = data.toString(Charsets.UTF_8)
Log.println(Log.ASSERT, "RESULT_LOGIN", returnJson)
callback(mapOf(Pair(true, returnJson)))
}
}
}
}
I invoke this login method at
val btnLogin = findViewById<Button>(R.id.btn_login)
btnLogin.setOnClickListener { _ ->
doAsync {
val username = findViewById<EditText>(R.id.input_username_login)
val password = findViewById<EditText>(R.id.input_password_login)
Login(username.text.toString(), password.text.toString()) {
// Request was successful
if (it.containsKey(true)) {
// Parse return Json
// e.g. {"id":"36e8fac0-487a-11e8-ad4e-c471feb11e42","token":"d6897a230fd7739e601649bf5fd89ea4b93317f6","expiry":"2018-04-27T17:49:48.721278Z"}
val jelement = JsonParser().parse(it.getValue(true))
val jobject = jelement.asJsonObject
// save field for class-scope access
Constants.token = jobject.get("token").asString
Constants.id = jobject.get("id").asString
}
else{
Toast.makeText(this#LoginActivity, it.getValue(false), Toast.LENGTH_SHORT).show()
}
}
}[30, TimeUnit.SECONDS]
var test = Constants.id;
}
In a separate Constants class, I store the token and id like this:
class Constants {
companion object {
val baseUrl: String = "BASE_URL_TO_MY_API"
val contentTypeJson = "application/json"
lateinit var STOREAGE_PATH: String
// current user details
lateinit var id: String
lateinit var token: String
lateinit var refresh_token: String
// logged in User
lateinit var user: User
}
How do I make sure that the test variable is set after the asynchronous task is done? Currently, I run into
lateinit property id has not been initialized
I have come across the option to limit the task to a timeout such as I have done with [30, TimeUnit.SECONDS], unfortunately, this did not help.
Thanks for the help! Cheers.
I think the problem is where you want to access the result:
val btnLogin = findViewById<Button>(R.id.btn_login)
btnLogin.setOnClickListener { _ ->
doAsync {
val username = findViewById<EditText>(R.id.input_username_login)
val password = findViewById<EditText>(R.id.input_password_login)
var test: String? = null
Login(username.text.toString(), password.text.toString()) {
// Request was successful
if (it.containsKey(true)) {
// Parse return Json
// e.g. {"id":"36e8fac0-487a-11e8-ad4e-c471feb11e42","token":"d6897a230fd7739e601649bf5fd89ea4b93317f6","expiry":"2018-04-27T17:49:48.721278Z"}
val jelement = JsonParser().parse(it.getValue(true))
val jobject = jelement.asJsonObject
// save field for class-scope access
Constants.token = jobject.get("token").asString
Constants.id = jobject.get("id").asString
}
else{
Toast.makeText(this#LoginActivity, it.getValue(false), Toast.LENGTH_SHORT).show()
}
}
test = Constants.id // here test variable surely set if result was successful, otherwise it holds the null value
test?.let{
resultDelivered(it)
}
}[30, TimeUnit.SECONDS]
}
fun resultDelivered(id: String){
// here we know that the async job has successfully finished
}
I'm trying to use AWS Cognito user pools in combination with the AWS API Gateway.
Logging in works fine, when entering the credentials the success handler is called and I can see the credentials. When verifying the JWT token at jwt.io I can also see that the user is correct.
However, when calling the API Gateway using the ApiClientFactory I always receive an error: com.amazonaws.mobileconnectors.apigateway.ApiClientException: Basic (classic) flow is not supported with RoleMappings, please use enhanced flow. (Service: AmazonCognitoIdentity; Status Code: 400; Error Code: InvalidParameterException; Request ID: 1a61f1fd-91d8-11e8-82bc-675071b1c307) (Service: null; Status Code: 0; Error Code: null; Request ID: null)
Please see my code below:
Main activity:
AWSMobileClient.getInstance().initialize(this) {
// Obtain the reference to the AWSCredentialsProvider and AWSConfiguration objects
// Use IdentityManager#getUserID to fetch the identity id.
IdentityManager.getDefaultIdentityManager().getUserID(object : IdentityHandler {
override fun onIdentityId(identityId: String) {
Log.d("MainActivity", "Identity ID = " + identityId)
}
override fun handleError(exception: Exception) {
Log.d("MainActivity", "Error in retrieving the identity" + exception)
}
})
}.execute()
LoginFragment:
val authenticationHandler = object : AuthenticationHandler {
override fun getAuthenticationDetails(continuation: AuthenticationContinuation, userID: String) {
val authDetails = AuthenticationDetails(inputUsername.text.toString(), inputPassword.text.toString(), null)
// Now allow the authentication to continue
continuation.setAuthenticationDetails(authDetails)
continuation.continueTask()
}
override fun onSuccess(userSession: CognitoUserSession, newDevice: CognitoDevice?) {
progressLoader.visibility = View.GONE
(activity as? OnboardingActivity)?.proceedAfterLogin()
}
override fun onFailure(exception: Exception) {
progressLoader.visibility = View.GONE
val snackbar = Snackbar.make(view, R.string.ERR_GENERAL, Snackbar.LENGTH_LONG)
snackbar.show()
progressLoader.visibility = View.GONE
}
override fun getMFACode(continuation: MultiFactorAuthenticationContinuation) {
continuation.continueTask()
}
override fun authenticationChallenge(continuation: ChallengeContinuation) {
continuation.continueTask()
}
}
loginButton.setOnClickListener {
val userPool = CognitoUserPool(context, AWSMobileClient.getInstance().configuration)
val user = userPool.getUser(inputUsername.text.toString())
progressLoader.visibility = View.VISIBLE
user.getSessionInBackground(authenticationHandler)
}
Api client:
val factory = ApiClientFactory().credentialsProvider(AWSMobileClient.getInstance().credentialsProvider)
val = factory.build(MyClient::class.java)
try {
val request = GetChallengesRequest("", nextPageKey)
val response = client.getRunningChallenges(request)
} catch (t: Throwable) {
// This catch is allways called with the error
}
The config is loaded using the awsconfiguration.json which is stored in the raw resource folder.
When setting a breakpoint in AWS4Signer sign method I can see the sign method is called with AnonymousAWSCredentials but I really can't figure out why, as I call the method after logging in.
I hope someone can help me resolve this weird issue, it's been bugging me for days!