Espresso: How to UI test Sms Retriever with User consent? - android

I am currently listening for Google Play services broadcasted action SmsRetriever.SMS_RETRIEVED_ACTION to check whether OTP SMS was retrieved. First, I start SmsRetriever:
SmsRetriever.getClient(context).startSmsUserConsent(null)
My BroadcastReceiver looks like this and works perfectly fine in real scenario:
object : BroadcastReceiver() {
override fun onReceive(
context: Context,
intent: Intent
) {
try {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val smsRetrieverStatus = extras?.get(SmsRetriever.EXTRA_STATUS) as? Status
when (smsRetrieverStatus?.statusCode) {
CommonStatusCodes.SUCCESS -> {
extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)?.let {
myOtpFragment.startActivityForResult(it, SMS_REQUEST_CODE)
}
}
}
}
} catch (e: Exception) {
Timber.e(e)
}
}
I want to somehow mock this onReceive method, in order to test and verify that my onActivityResult code works as expected and autofills EditText with retrieved OTP.
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SMS_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val otp = data?.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE) ?: ""
et_otp?.let {
it.setText(otp)
adjustOtpSelection(it)
}
}
Any tips on how could I do that with Espresso and Mockito?

Related

registerForActivityResult and onActivityResult onActivityResult in Kotlin

I am currently trying to learn from a step-by-step tutorial to upload an Image or File to my server while using Volley. This tutorial is a little bit outdated and I really don't understand how I can fix these issues.
the tutorial
onActivityResult(Int, Int, Intent?): Unit' is deprecated. Deprecated in Java
Fragment is attempting to registerForActivityResult after being created. Fragments must call registerForActivityResult() before they are created (i.e. initialization, onAttach(), or onCreate()).
My code
//Uploading Photos
private fun launchGallery() {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
// There are no request codes
val data: Intent? = result.data
}
}
}
private fun uploadImage() {
imageData?: return
val request = object : VolleyFileUploadRequest(
Request.Method.POST,
postURL,
{
println("response is: $it")
},
{
println("error is: $it")
}
) {
override fun getByteData(): MutableMap<String, FileDataPart> {
var params = HashMap<String, FileDataPart>()
params["imageFile"] = FileDataPart("image", imageData!!, "jpeg")
return params
}
}
Volley.newRequestQueue(requireContext()).add(request)
}
#Throws(IOException::class)
private fun createImageData(uri: Uri) {
val inputStream = requireContext().contentResolver.openInputStream(uri)
inputStream?.buffered()?.use {
imageData = it.readBytes()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_PICK_CODE) {
val uri = data?.data
if (uri != null) {
imageView.setImageURI(uri)
createImageData(uri)
}
}
super.onActivityResult(requestCode, resultCode, data)
}
}
you are trying to registerForActivityResult from a method "launchGallery()" which is not the way it should be done, "registerForActivityResult" should be initialized in the Activity/Fragment onCreate function and then you can use the "resultLauncher" variable to open the gallery/camera etc...
also when you are using the "registerForActivityResult" you don't need to override "onActivityResult" (which is now deprecated).
check the Activity Result Api to get a better understanding of how the new api works.
and here is a good tutorial that shows you how to use the Android Activity Result API for selecting and taking images

Why do I get a type mismatch when using "this" on fragment Kotlin

I can scan a barcode successfully but I somehow cannot get the result. I found out that since I am calling the barcode scanner in a fragment, I need change my code to use this:
class AddIerFragment : Fragment() { ....
val intentIntegrator = IntentIntegrator.forFragment(this)
....
}
The problem is, the "this" keyword is not allowed because it gives me an error of
Type mismatch
Requred: Fragment
Found AddIerFragment
See image below.
I have this code in the fragment
companion object {
#JvmStatic
fun newInstance(param1: String, param2: String) =
AddIerFragment().apply {
arguments = Bundle().apply {
}
}
private const val CAMERA = 1
private const val GALLERY = 2
private const val SCAN = 3
}
R.id.button_atgScan -> {
Dexter.withContext(context!!).withPermissions(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.CAMERA
)
.withListener(object: MultiplePermissionsListener {
override fun onPermissionsChecked(report: MultiplePermissionsReport?) {
report?.let{
if(report!!.areAllPermissionsGranted()) {
intentIntegrator.setDesiredBarcodeFormats(IntentIntegrator.ONE_D_CODE_TYPES)
intentIntegrator.setPrompt("Scan a barcode")
intentIntegrator.setCameraId(0)
intentIntegrator.setBeepEnabled(false)
intentIntegrator.setBarcodeImageEnabled(true)
intentIntegrator.setOrientationLocked(false)
intentIntegrator.initiateScan()
}
}
}
override fun onPermissionRationaleShouldBeShown(
p0: MutableList<PermissionRequest>?,
p1: PermissionToken?
) {
showRationalDialogForPermission()
}
}).onSameThread().check()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == GALLERY) {
data?.let {
val selectedPhotoUri = data.data
file = File(getPath(selectedPhotoUri))
gView!!.iv_ier_image.setImageURI(selectedPhotoUri)
}
} else if (requestCode == CAMERA) {
data?.extras?.let {
val thumbnail: Bitmap =
data.extras!!.get("data") as Bitmap
file = savebitmap(thumbnail)!!
gView!!.iv_ier_image.setImageBitmap(thumbnail)
}
}
val result = IntentIntegrator.parseActivityResult(requestCode, resultCode, data)
if (result != null) {
if (result.contents == null) {
Log.i("TAG", "NOTHING")
} else {
Log.i("TAG", result.contents)
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
For support or androidx fragments you should use IntentIntegrator.forSupportFragment(this)
AddIerFragment must not be a subclass of the correct Fragment. At the top of its file, make sure you have imported androidx.fragment.app.Fragment instead of android.app.Fragment. And assuming you're using zxing-android-embedded, make sure you call forSupportFragment, not forFragment.

Default way to get request code in ActivityResultContract

Is there any default way to get the request code in ActivityResultContract?
I know about StartActivityForResult contract, which returns ActivityResult, but there is no requestCode, only resultCode.
I can do something like this, but maybe there is a better solution:
class StartActivityForResult : ActivityResultContract<ActivityInput, ActivityOutput?>() {
override fun createIntent(
context: Context,
input: ActivityInput
): Intent {
return input.data.apply { putExtra(requestCodeKey, input.requestCode) }
}
override fun parseResult(resultCode: Int, intent: Intent?): ActivityOutput? {
return if (intent == null || resultCode != Activity.RESULT_OK) null
else ActivityOutput(
// should never return default value
requestCode = intent.getIntExtra(requestCodeKey, -1),
resultCode = resultCode,
data = intent
)
}
override fun getSynchronousResult(
context: Context,
input: ActivityInput?
): SynchronousResult<ActivityOutput?>? {
return if (input == null) SynchronousResult(null) else null
}
companion object {
const val requestCodeKey = "requestCodeKey";
}
}
data class ActivityInput(val requestCode: Int, val data: Intent)
data class ActivityOutput(val requestCode: Int,
val resultCode: Int,
val data: Intent
)

Android SMS Verification API result code is always 0

I implemented the Android SMS Verification API on activities and fragments on the same project and it went well. My problem is with fragments in tabs. No matter what I do, onActivityResult always returns result code 0 when "Allow" is pressed. Here's my lot of code which was also implemented and tested to be working on the activities and fragments.
override fun onStart() {
super.onStart()
registerToSmsBroadcastReceiver()
}
override fun onStop() {
myActivity.unregisterReceiver(smsBroadcastReceiver)
super.onStop()
}
private fun startSmsUserConsent() {
SmsRetriever.getClient(myActivity).also {
it.startSmsUserConsent(null)
.addOnSuccessListener {
Log.d("LISTENING", "SUCCESS")
}
.addOnFailureListener {
Log.d("LISTENING", "FAIL")
}
}
}
private fun registerToSmsBroadcastReceiver() {
smsBroadcastReceiver = SmsBroadcastReceiver().also {
it.smsBroadcastReceiverListener =
object : SmsBroadcastReceiver.SmsBroadcastReceiverListener {
override fun onSuccess(intent: Intent?) {
intent?.let { context -> startActivityForResult(context, REQ_USER_CONSENT) }
}
override fun onFailure() {
}
}
}
val intentFilter = IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION)
myActivity.registerReceiver(smsBroadcastReceiver, intentFilter)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQ_USER_CONSENT -> {
if ((resultCode == Activity.RESULT_OK) && (data != null)) {
val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE)
val code = message?.let { fetchVerificationCode(it) }
otpField.setText(code)
}
}
}
}
private fun fetchVerificationCode(message: String): String {
return Regex("(\\d{6})").find(message)?.value ?: ""
}
Oh, and startSmsUserConsent() is called whenever I call for the API to send an OTP. Anything I missed?
Thank you.
I solved the issue by handling the OTP SMS Retrieval on the activity instead of on the fragment, then passed on the fragment if need be.

how to use repository or viewmodel method in onAvitivityResult

I am trying to follow MVVM pattern in android project, I have to call network api onAcitivityResult method. According to MVVM repository should interact with network calls and viewmodel should do the interaction between Activity and repository. So if I have to access network api then I have to call viewmodel method in onActivityResult. This is my onActivityResult method:
class Profile : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val networkConnectionInterceptor = NetworkConnectionInterceptor(this)
val api = Api.invoke(networkConnectionInterceptor)
val repository = UserRepository(api)
val factory = ProfileViewModelFactory(repository, Photo(""))
val viewModel = ViewModelProvider(this, factory).get(ProfileViewModel::class.java)
val binding: ActivityProfileBinding =
DataBindingUtil.setContentView(this, R.layout.activity_profile)
binding.viewmodel = viewModel
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(resultCode == Activity.RESULT_OK) {
if (data != null) {
when (requestCode) {
ImageIntent.CAMERA_REQUEST -> {
/* I want to call Viewmodel method here */
viewmodel.onProfileImageUpload(ImageIntent.imageUri)
}
}
}
} else if (resultCode == Activity.RESULT_CANCELED) {
toast("Image upload cancelled !")
}
}
This is the method defined in my viewmodel :
fun onProfileImageUpload(uri: Uri) {
Coroutines.main{
try {
val imageResponse = repository.updateProfileAvatar(
ImageUtil.getImageForUpload(
uri,
"avatar"
)
)
Log.d("avatar_resonse", "$imageResponse")
} catch(e : Exception) {}
}
}
The problem is I have to initialize the viewmodel in Activity onCreate method so I cannot have the viewmodel instance in the onActivityResult. How do I make a network call from there ?
try this
if(requestCode==your code){
if(resultCode==Activity.RESULT_OK){
if(data!=null){
// your api and if you calling image from start activity result get it from data
}
}
}

Categories

Resources