Targeting S+ (version 31 and above) requires that one of FLAG_IMMUTABLE or FLAG_MUTABLE be specified when creating a PendingIntent? - android

i'm making login application with firebase, I enabled Email/password and google providers both are working perfectly on Api less than 31 but on levels above that google works but Email/password doesn't and i don't know the problem.
What i tried to do :
-updated all dependencies to last version.
-added implementation 'androidx.work:work-runtime-ktx:2.7.0'
-and every solution I found.enter image description here
class AuthenticationActivity : AppCompatActivity() {
private lateinit var binding: ActivityAuthenticationBinding
private val viewModel by viewModels<LoginViewModel>()
companion object {
const val TAG = "LoginFragment"
const val SIGN_IN_RESULT_CODE = 1001
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityAuthenticationBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.authButton.setOnClickListener { launchSignInFlow() }
viewModel.authenticationState.observe(this) { authenticationState ->
when (authenticationState) {
LoginViewModel.AuthenticationState.AUTHENTICATED -> switchActivities()
else -> Log.e(
TAG,
"Authentication state that doesn't require any UI change $authenticationState"
)
}
}
}
private fun launchSignInFlow() {
val providers = arrayListOf(
EmailBuilder().build(), GoogleBuilder().build()
)
startActivityForResult(
AuthUI.getInstance().createSignInIntentBuilder().setAvailableProviders(providers)
.build(), AuthenticationActivity.SIGN_IN_RESULT_CODE
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == SIGN_IN_RESULT_CODE) {
val response = IdpResponse.fromResultIntent(data)
if (resultCode == Activity.RESULT_OK) {
startActivity(Intent(this#AuthenticationActivity, MyApp::class.java))
finish()
return
} else {
if (response == null) {
Log.e("Login", "Login canceled by User")
return
}
if (response.error!!.errorCode == ErrorCodes.NO_NETWORK) {
Log.e("Login", "No Internet Connection")
return
}
if (response.error!!.errorCode == ErrorCodes.UNKNOWN_ERROR) {
Log.e("Login", "Unknown Error")
return
}
}
Log.e("Login", "Unknown sign in response")
}
}
private fun switchActivities() {
val switchActivityIntent = Intent(this, RemindersActivity::class.java)
startActivity(switchActivityIntent)
}
}
private fun sendNotification(triggeringGeofences: List<Geofence>) {
triggeringGeofences.forEach {
val requestId = it.requestId
val remindersRepository: ReminderDataSource by inject()
//Interaction to the repository has to be through a coroutine scope
CoroutineScope(coroutineContext).launch(SupervisorJob()) {
//get the reminder with the request id
val result = remindersRepository.getReminder(requestId)
if (result is Result.Success<ReminderDTO>) {
val reminderDTO = result.data
//send a notification to the user with the reminder details
sendNotification(
this#GeofenceTransitionsJobIntentService, ReminderDataItem(
reminderDTO.title,
reminderDTO.description,
reminderDTO.location,
reminderDTO.latitude,
reminderDTO.longitude,
reminderDTO.id
)
)
}
}
}
}

That was an issue on older versions of FirebaseUI for Android. It has been fixed in version 8.0.2. Please update the dependency in your build.gradle file:
implementation 'com.firebaseui:firebase-ui-auth:8.0.2'

Related

Flutter: It's possible to send data from Android module to Flutter project via MethodChannel without close Activity(Still in Android Activity)?

I have screens.
StoryScreen (from Flutter)
MainActivity (from Android Activity, extend to FlutterActivity)
MainUnityActivity (from Android Activity, extend to AppCompatActivity)
The actually screen is only two, StoryScreen and MainUnityActivity. MainActivity only for host.
In MainActivity we define intent and method channel.
class MainActivity : FlutterActivity() {
private val tag = "MAIN"
private val channel = "NATIVE_EXPERIMENT"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor, channel).setMethodCallHandler { call, result ->
if (call.method.equals("goToUnityActivity")) {
val arg = call.arguments as Map<String, Any>
val gameType = arg.getValue("gameType") as String
val catalogURL = arg.getValue("catalogURL") as String
goToUnityActivity(gameType, catalogURL)
result.success(null)
} else {
result.notImplemented()
}
}
}
private fun goToUnityActivity(gameType: String, catalogURL: String) {
val intent = Intent(this, MainUnityActivity::class.java)
intent.putExtra("gameType", gameType)
intent.putExtra("catalogURL", catalogURL)
startActivityForResult(intent, MainUnityActivity.REQUEST_CODE_FROM_UNITY)
}
private fun notifyFlutterBackFromUnity(data: String?) {
MethodChannel(flutterEngine?.dartExecutor, channel).invokeMethod("backFromUnity", data)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == MainUnityActivity.REQUEST_CODE_FROM_UNITY) {
if (resultCode == MainUnityActivity.RESULT_CODE_BACK_FROM_UNITY) {
val listOfReport = data!!.getStringExtra("listOfReport")
Log.i(tag, "/// [MainActivity] back from unity --> $listOfReport")
notifyFlutterBackFromUnity(listOfReport)
}
}
}
}
If I exit from MainUnityActivity, I can send data from Android to Flutter side, but how if we still in MainUnityActivity but want to send data from Android to Flutter side?
class MainUnityActivity : UnityPlayerActivity() {
private val tag = "MIDDLEWARE_UNITY"
private val listOfVehicleNames: MutableList<String> = mutableListOf()
companion object {
const val RESULT_CODE_BACK_FROM_UNITY = 110
const val REQUEST_CODE_FROM_UNITY = 1
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
listOfVehicleNames.clear()
}
private fun backFromUnity() {
Log.i(tag, "/// [MainUnityActivity] BackButtonClick")
val resultIntent = Intent().apply {
putExtra("listOfReport", listOfVehicleNames.toString())
}
setResult(RESULT_CODE_BACK_FROM_UNITY, resultIntent)
finish()
}
override fun BackButtonClick() {
Log.i(tag, "/// [MainUnityActivity] BackButtonClick")
backFromUnity()
}
override fun ReportButtonClick() {
Log.i(tag, "/// [MainUnityActivity] ReportButtonClick")
showDialog()
}
private fun notifyFlutterReportFromUnity() {
val json = """{"title": "JSON Title", "notes": "JSON Notes"}"""
Log.i(tag, "/// [MainUnityActivity] YesReportClick :$json")
listOfVehicleNames.add(json)
// TODO: I want send data to Flutter without close this activity
}
private fun showDialog() {
val builder = AlertDialog.Builder(this)
builder.setTitle("Report")
builder.setMessage("Is there something wrong ?")
builder.setPositiveButton(
"Yes"
) { _, _ ->
Toast.makeText(this, "Okay, we're sorry", Toast.LENGTH_SHORT).show()
notifyFlutterReportFromUnity()
}
builder.setNegativeButton(
"No"
) { _, _ ->
// User click no
}
builder.setNeutralButton("Cancel") { _, _ ->
// User cancelled the dialog
}
builder.show()
}
}
Finally, I fix it using MethodChannel, EventChannel, and BroadcastReceiver.
Method channel: A named channel for communicating with platform plugins using asynchronous method calls.
Event Channel: A named channel for communicating with platform plugins using event streams.
The example project is here: https://github.com/rrifafauzikomara/example_flutter_method_channel

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.

Login with google work from android studio but don't work from google play

I built an app with google login.
When I connect the device to the android studio the log in work perfect, But when I download my app from google play it fails to connect for some reason...
I tried to find a solution and I couldn't find one...
That's the code ... I would love if anyone has any idea what the problem is..
Thanks for your time!
class firstActivity : AppCompatActivity(){
private lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
auth = FirebaseAuth.getInstance()
val acct = GoogleSignIn.getLastSignedInAccount(this)
if (acct != null) {
val personName = acct.displayName
val personGivenName = acct.givenName
val personFamilyName = acct.familyName
val personEmail = acct.email
val personId = acct.id
}
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(getString(R.string.default_web_client_idd))
.requestEmail()
.build()
val mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
signinButtonGoogle.setOnClickListener {
val signInIntent: Intent = mGoogleSignInClient.getSignInIntent()
startActivityForResult(signInIntent, Companion.RC_SIGN_IN)
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
GoogleSignInApi.getSignInIntent(...);
if (requestCode == Companion.RC_SIGN_IN) {
val task = GoogleSignIn.getSignedInAccountFromIntent(data)
try {
val account = task.getResult(ApiException::class.java)
firebaseAuthWithGoogle(account!!)
} catch (e: ApiException) {
Toast.makeText(applicationContext, "Google sign in failed", Toast.LENGTH_SHORT)
.show()
}
}
}
private fun firebaseAuthWithGoogle(acct: GoogleSignInAccount) {
val progressDialog = ProgressDialog(this)
progressDialog.setMessage("Connecting...")
progressDialog.setCancelable(false)
progressDialog.show()
val credential = GoogleAuthProvider.getCredential(acct.idToken, null)
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
val user = auth.currentUser
signinButtonGoogle.visibility = View.VISIBLE
Handler().postDelayed({ progressDialog.dismiss() }, 0)
val leagueIntent = Intent(this, homeActivity::class.java)
startActivity(leagueIntent)
} else {
Toast.makeText(applicationContext, "Authentication Failed.", Toast.LENGTH_SHORT).show()
signinButtonGoogle.visibility = View.VISIBLE
Handler().postDelayed({ progressDialog.dismiss() }, 0)
}
}
}
companion object {
const val RC_SIGN_IN = 123
}
}
because google play have another SH1 for relase , so you can find it like image and goto firbase manger for your prokect then add new one ,
image

How to prevent memory leak with In-App Update Library

I want to implement the new In-App Update library in my app, but I've noticed that it trigger a memory leak in my activity when it's recreated/rotated.
Here's the only detail I have from LeakCanary:
Obviously, I've nothing if I remove the code from the In-App Update lib especially the addOnSuccessListener :
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
updateInfo.value = appUpdateInfo
updateAvailable.value = true
}else{
updateInfo.value = null
updateAvailable.value = false
}
}
According to this post, I have first used some LiveData, but the problem was the same, so I used a full class to handle the callback, with LiveData :
My Service class :
class AppUpdateService {
val updateAvailable: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateDownloaded: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateInfo: MutableLiveData<AppUpdateInfo> by lazy { MutableLiveData<AppUpdateInfo>() }
fun checkForUpdate(appUpdateManager: AppUpdateManager){
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
updateInfo.value = appUpdateInfo
updateAvailable.value = true
}else{
updateInfo.value = null
updateAvailable.value = false
}
}
}
fun checkUpdateOnResume(appUpdateManager: AppUpdateManager){
appUpdateManager.appUpdateInfo.addOnSuccessListener {
updateDownloaded.value = (it.installStatus() == InstallStatus.DOWNLOADED)
}
}
}
My Activity simplified :
class MainActivity : BaseActivity(), InstallStateUpdatedListener {
override fun contentViewID(): Int { return R.layout.activity_main }
private val UPDATE_REQUEST_CODE = 8000
private lateinit var appUpdateManager : AppUpdateManager
private val appUpdateService = AppUpdateService()
override fun onStateUpdate(state: InstallState?) {
if(state?.installStatus() == InstallStatus.DOWNLOADED){ notifyUser() }
}
// Called in the onCreate()
override fun setupView(){
appUpdateManager = AppUpdateManagerFactory.create(this)
appUpdateManager.registerListener(this)
setupAppUpdateServiceObservers()
// Check for Update
appUpdateService.checkForUpdate(appUpdateManager)
}
private fun setupAppUpdateServiceObservers(){
appUpdateService.updateAvailable.observe(this, Observer {
if (it)
requestUpdate(appUpdateService.updateInfo.value)
})
appUpdateService.updateDownloaded.observe(this, Observer {
if (it)
notifyUser()
})
}
private fun requestUpdate(appUpdateInfo: AppUpdateInfo?){
appUpdateManager.startUpdateFlowForResult(appUpdateInfo, AppUpdateType.FLEXIBLE, this, UPDATE_REQUEST_CODE)
}
private fun notifyUser(){
showSnackbar(getString(R.string.updated_downloaded), getString(R.string.restart)) {
appUpdateManager.completeUpdate()
appUpdateManager.unregisterListener(this)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == UPDATE_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Timber.d("Update flow failed! Result code: $resultCode")
}
}
}
override fun onDestroy() {
appUpdateManager.unregisterListener(this)
super.onDestroy()
}
override fun onResume() {
super.onResume()
appUpdateService.checkUpdateOnResume(appUpdateManager)
}
}
I don't really understand how to avoid the memory leak as the appUpdateManager has to be created with the context of the activity, and it looks to be the thing that causes the memory leak with the callback.
Does someone already implement it without having this issue?
Using weak reference to the context will probably solve your memory leak problem. Write this in your activity:
WeakReference<Context> contextWeakReference = new WeakReference<Context>(this);
Context context = contextWeakReference.get();
if (context != null) {
// Register using context here
}
There are lots of good articles on WeakReference, Garbage Collection and Memory Leaks to read more on the subject.
Also, onDestroy() is not guaranteed to be called. When you start another Activity, onPause() and onStop() method called instead of onDestroy().
The onDestroy() calls when you hit back button or call finish() method. So, unregister Listener in onPause() or onStop(). If you unregister in onDestroy() method, it might cause a memory leak.
Another idea is that since AppUpdateService class in not a subclass of ViewModel, it is not lifecycle aware. I'm not sure, but, you might need to remove observers in onstop/onDestroy of the activity and add them in onResume. (observers has a strong reference to the LifecycleOwner, here the activiy) To do that you need to define observers to be able to remove them later. Something like:
MutableLiveData<Boolean> someData = new MutableLiveData<>;
and then in onResume:
someData = appUpdateService.updateAvailable;
someData.observe()
and in onStop:
someData.removeObservers()
It's just a guess, but, I hope it would help somehow.
Thanks to #Sina Farahzadi I searched and try a lot of things and figured that the problem was the appUpdateManager.appUdateInfo call with the Task object.
The way I found to solve the memory leak is to use the applicationContext instead of the context of the activity. I'm not sure it's the best solution, but it's the one I've found for now. I've exported all in my service class so here's my code :
AppUpdateService.kt :
class AppUpdateService : InstallStateUpdatedListener {
val updateAvailable: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateDownloaded: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val notifyUser: MutableLiveData<Boolean> by lazy { MutableLiveData<Boolean>() }
val updateInfo: MutableLiveData<AppUpdateInfo> by lazy { MutableLiveData<AppUpdateInfo>() }
private var appUpdateManager : AppUpdateManager? = null
private var appUpdateInfoTask: Task<AppUpdateInfo>? = null
override fun onStateUpdate(state: InstallState?) {
notifyUser.value = (state?.installStatus() == InstallStatus.DOWNLOADED)
}
fun setupAppUpdateManager(context: Context){
appUpdateManager = AppUpdateManagerFactory.create(context)
appUpdateManager?.registerListener(this)
checkForUpdate()
}
fun onStopCalled(){
appUpdateManager?.unregisterListener(this)
appUpdateInfoTask = null
appUpdateManager = null
}
fun checkForUpdate(){
appUpdateInfoTask = appUpdateManager?.appUpdateInfo
appUpdateInfoTask?.addOnSuccessListener { appUpdateInfo ->
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)){
updateInfo.value = appUpdateInfo
updateAvailable.value = true
}else{
updateInfo.value = null
updateAvailable.value = false
}
}
}
fun startUpdate(activity: Activity, code: Int){
appUpdateManager?.startUpdateFlowForResult(updateInfo.value, AppUpdateType.FLEXIBLE, activity, code)
}
fun updateComplete(){
appUpdateManager?.completeUpdate()
appUpdateManager?.unregisterListener(this)
}
fun checkUpdateOnResume(){
appUpdateManager?.appUpdateInfo?.addOnSuccessListener {
updateDownloaded.value = (it.installStatus() == InstallStatus.DOWNLOADED)
}
}
}
MainActivity simplified :
class MainActivity : BaseActivity(){
override fun contentViewID(): Int { return R.layout.activity_main }
private val UPDATE_REQUEST_CODE = 8000
private var appUpdateService: AppUpdateService? = AppUpdateService()
/**
* Setup the view of the activity (navigation and menus)
*/
override fun setupView(){
val contextWeakReference = WeakReference<Context>(applicationContext)
contextWeakReference.get()?.let {weakContext ->
appUpdateService?.setupAppUpdateManager(weakContext)
}
}
private fun setupAppUpdateServiceObservers(){
appUpdateService?.updateAvailable?.observe(this, Observer {
if (it)
requestUpdate()
})
appUpdateService?.updateDownloaded?.observe(this, Observer {
if (it)
notifyUser()
})
appUpdateService?.notifyUser?.observe(this, Observer {
if (it)
notifyUser()
})
}
private fun removeAppUpdateServiceObservers(){
appUpdateService?.updateAvailable?.removeObservers(this)
appUpdateService?.updateDownloaded?.removeObservers(this)
appUpdateService?.notifyUser?.removeObservers(this)
}
private fun requestUpdate(){
appUpdateService?.startUpdate(this, UPDATE_REQUEST_CODE)
}
private fun notifyUser(){
showSnackbar(getString(R.string.updated_downloaded), getString(R.string.restart)) {
appUpdateService?.updateComplete()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == UPDATE_REQUEST_CODE) {
if (resultCode != RESULT_OK) {
Timber.d("Update flow failed! Result code: $resultCode")
}
}
}
override fun onStop() {
appUpdateService?.onStopCalled()
removeAppUpdateServiceObservers()
appUpdateService = null
super.onStop()
}
override fun onResume() {
super.onResume()
setupAppUpdateServiceObservers()
appUpdateService?.checkUpdateOnResume()
}
}
For now, I will keep it that way and continue to search for another way to do it.
Let me know if someone has a better way to do it.
Use this helper class:
class GoogleUpdater(activity: FragmentActivity) : LifecycleObserver {
private val appUpdateManager = AppUpdateManagerFactory.create(activity)
private var installStateUpdatedListener: InstallStateUpdatedListener? = null
private var wra = WeakReference(activity)
private val activity get() = wra.get()
init {
activity.lifecycle.addObserver(this)
}
fun checkUpdate() {
fun showCompleteUpdateDialog() {
activity?.let { activity ->
if (!activity.isFinishing)
AlertDialog.Builder(activity)
.setTitle(R.string.notification)
.setMessage(R.string.restart_to_complete_update)
.setIcon(ContextCompat.getDrawable(activity, R.drawable.ic_notification)
?.apply {
mutate()
alpha = 127
})
.setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> appUpdateManager.completeUpdate() }
.setNegativeButton(R.string.no, null)
.create()
.apply { setCanceledOnTouchOutside(false) }
.show()
}
}
installStateUpdatedListener = object : InstallStateUpdatedListener {
override fun onStateUpdate(state: InstallState) {
if (state.installStatus() == InstallStatus.DOWNLOADED)
showCompleteUpdateDialog()
else if (state.installStatus() == InstallStatus.INSTALLED)
appUpdateManager.unregisterListener(this)
}
}.also { appUpdateManager.registerListener(it) }
appUpdateManager.appUpdateInfo.addOnSuccessListener { appUpdateInfo ->
val clientVersionStalenessDays = appUpdateInfo.clientVersionStalenessDays()
if (appUpdateInfo.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE
&& appUpdateInfo.isUpdateTypeAllowed(AppUpdateType.FLEXIBLE)
&& clientVersionStalenessDays != null
&& clientVersionStalenessDays >= DAYS_FOR_FLEXIBLE_UPDATE) {
try {
activity?.let { activity ->
if (!activity.isFinishing)
appUpdateManager.startUpdateFlowForResult(
appUpdateInfo,
AppUpdateType.FLEXIBLE,
activity,
REQUEST_CODE_APP_UPDATE)
}
} catch (e: SendIntentException) {
FirebaseCrashlytics.getInstance().recordException(e)
}
} else if (appUpdateInfo.installStatus() == InstallStatus.DOWNLOADED)
showCompleteUpdateDialog()
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
private fun onStop() {
installStateUpdatedListener?.let { appUpdateManager.unregisterListener(it) }
}
companion object {
const val REQUEST_CODE_APP_UPDATE = 11
const val DAYS_FOR_FLEXIBLE_UPDATE = 1
}
}
In Activity:
GoogleUpdater(this).apply { checkUpdate() }

How to check permission is granted in ViewModel?

I need to ask permission for contacts and when application starts I'm asking,in ViewModel part I need to call method which requires permission. I need to check permission is granted by user or not and then call, but for checking permission I need to have access Activity. while in my ViewModel I don't have a reference to Activity and don't want to have, How I can overcome, the problem?
I just ran into this problem, and I decided to use make use of LiveData instead.
Core concept:
ViewModel has a LiveData on what permission request needs to be made
ViewModel has a method (essentially callback) that returns if permission is granted or not
SomeViewModel.kt:
class SomeViewModel : ViewModel() {
val permissionRequest = MutableLiveData<String>()
fun onPermissionResult(permission: String, granted: Boolean) {
TODO("whatever you need to do")
}
}
FragmentOrActivity.kt
class FragmentOrActivity : FragmentOrActivity() {
private viewModel: SomeViewModel by lazy {
ViewModelProviders.of(this).get(SomeViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
......
viewModel.permissionRequest.observe(this, Observer { permission ->
TODO("ask for permission, and then call viewModel.onPermissionResult aftwewards")
})
......
}
}
I have reworked the solution. The PermissionRequester object is everything you need to request permissions from any point where you have at least an application context. It uses its helper PermissionRequestActivity to accomplish this job.
#Parcelize
class PermissionResult(val permission: String, val state: State) : Parcelable
enum class State { GRANTED, DENIED_TEMPORARILY, DENIED_PERMANENTLY }
typealias Cancellable = () -> Unit
private const val PERMISSIONS_ARGUMENT_KEY = "PERMISSIONS_ARGUMENT_KEY"
private const val REQUEST_CODE_ARGUMENT_KEY = "REQUEST_CODE_ARGUMENT_KEY"
object PermissionRequester {
private val callbackMap = ConcurrentHashMap<Int, (List<PermissionResult>) -> Unit>(1)
private var requestCode = 256
get() {
requestCode = field--
return if (field < 0) 255 else field
}
fun requestPermissions(context: Context, vararg permissions: String, callback: (List<PermissionResult>) -> Unit): Cancellable {
val intent = Intent(context, PermissionRequestActivity::class.java)
.putExtra(PERMISSIONS_ARGUMENT_KEY, permissions)
.putExtra(REQUEST_CODE_ARGUMENT_KEY, requestCode)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
callbackMap[requestCode] = callback
return { callbackMap.remove(requestCode) }
}
internal fun onPermissionResult(responses: List<PermissionResult>, requestCode: Int) {
callbackMap[requestCode]?.invoke(responses)
callbackMap.remove(requestCode)
}
}
class PermissionRequestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
requestPermissions()
}
}
private fun requestPermissions() {
val permissions = intent?.getStringArrayExtra(PERMISSIONS_ARGUMENT_KEY) ?: arrayOf()
val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1
when {
permissions.isNotEmpty() && requestCode != -1 -> ActivityCompat.requestPermissions(this, permissions, requestCode)
else -> finishWithResult()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val permissionResults = grantResults.zip(permissions).map { (grantResult, permission) ->
val state = when {
grantResult == PackageManager.PERMISSION_GRANTED -> State.GRANTED
ActivityCompat.shouldShowRequestPermissionRationale(this, permission) -> State.DENIED_TEMPORARILY
else -> State.DENIED_PERMANENTLY
}
PermissionResult(permission, state)
}
finishWithResult(permissionResults)
}
private fun finishWithResult(permissionResult: List<PermissionResult> = listOf()) {
val requestCode = intent?.getIntExtra(REQUEST_CODE_ARGUMENT_KEY, -1) ?: -1
PermissionRequester.onPermissionResult(permissionResult, requestCode)
finish()
}
}
Usage:
class MyViewModel(application: Application) : AndroidViewModel(application) {
private val cancelRequest: Cancellable = requestPermission()
private fun requestPermission(): Cancellable {
return PermissionRequester.requestPermissions(getApplication(), "android.permission.SEND_SMS") {
if (it.firstOrNull()?.state == State.GRANTED) {
Toast.makeText(getApplication(), "GRANTED", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(getApplication(), "DENIED", Toast.LENGTH_LONG).show()
}
}
}
override fun onCleared() {
super.onCleared()
cancelRequest()
}
}
I did something like this:
create an abstract class that extends AndroidViewModel which gives you access to the application context:
abstract class BaseViewModel(application: Application) : AndroidViewModel(application), CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Now, create your view model by extending the BaseViewModel class and you will have access to the application context
class AdminViewModel(application: Application) : BaseViewModel(application) {
.....
}
Now you always have access to a Context that you can use to get access to resources.

Categories

Resources