Cannot pass context between two classes [kotlin] - android

So, I am new to Kotlin.
I have two classes LoginActivity and DashboardActivity.
In the DashboardActivity I have a method:
fun createActivity(context: Context){
val intent = Intent(context, DashboardActivity::class.java)
startActivity(intent)
}
and in the LoginActivity, when the user clicks the button to login, I have a method that calls the previous method:
private fun onClickLogin() {
val username : String = editTextUsername.text.toString()
val password : String = editTextPassword.text.toString()
if (username.isEmpty()){
editTextUsername.error = "Insira um username"
return
}
if (password.isEmpty()){
editTextPassword.error = "Insira uma password"
return
}
if (username.compareTo("user") == 0 && password.compareTo("password") == 0){
DashboardActivity().createActivity(this)
}
}
The problem is, every time I try to call DashboardActivity().createActivity(this), I get a NullPointerException.
Here is the logcat:
Click here, please
So, why do I get this error? Is it because I cannot pass the context like this?
I have tried using this#LoginActivity but didn't work...

you can't do that:
DashboardActivity().createActivity(this)
in that line you are trying to initialize activity and then use a method.
What you should do is:
1.
startActivity(DashboardActivity.createActivity(this))
2. In DashboardActivity.kt
companion object {
fun createActivity(context: Context): Intent {
return Intent(context, DashboardActivity::class.java)
}
}
This creates createActivity method as "static" which means that you can access without need to initilize an intsance of this class.

use ApplicationContext
fun createActivity(){
val intent = Intent(applicationContext, DashboardActivity::class.java)
startActivity(intent)
}

every time I try to call DashboardActivity().createActivity(this), I get a NullPointerException
Never create an instance of an activity class yourself.
why do I get this error?
Because you created an instance of DashboardActivity yourself and tried using it.
I have tried using this#LoginActivity but didn't work...
Make createActivity() be a function on a companion object. This will require a slight change to your implementation, calling startActivity() on the passed-in Context.

Related

I want my library to start an activity without the main code explictly calling it

I'm working on a library that has a couple of ready-made activities.
So far i have my activities in the library, and in the main app, i call it normally with registerForActivityResult to start it.
this means whoever is using my library would be able to see the whole activity.
what i would like to do, is to have the developer call a method in the library class and ask it to do an action, and in the library that method would on its own start the activity, register it for result, and return the result to the calling class through an interface.
the below is what i tried but it gives me error LifecycleOwner is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED
private fun launchScannerActivity(activity: FragmentActivity, callback: ScannerCallback) {
val scanResult =
activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
callback.onResult(it.data?.getStringExtra("Some Key") ?: "")
} else {
callback.onFail()
}
}
val intent = Intent(activity, ScannerActivity::class.java)
scanResult.launch(intent);
}
why do i need this:
This library would be an SDK for a SAAS product, so we would like to abstract and obfuscate as much of the implementation as possible from our clients.
You can't really communicate between Activities using interfaces, at least not in a way that is somewhat concise and isn't very prone to leaking. What you can do is expose your own Activity result contract. Then your API could be as simple as some of the ones in ActivityResultContracts. You can look at the source code there to see how to implement it.
Maybe something like this:
class ScannerResultContract : ActivityResultContract<Unit, String?>() {
override fun createIntent(context: Context, input: Unit?): Intent {
return Intent(context, ScannerActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
return if (resultCode == Activity.RESULT_OK) {
intent?.getStringExtra("Some Key")
} else {
null
}
}
}
Client usage:
// In activity or fragment:
val getScannerResult = registerForActivityResult(ScannerResultContract()) { resultString ->
if (resultString != null) {
// use it
} else {
// log no result returned
}
}
//elsewhere:
someListener.setOnClickListener {
getScannerResult.launch()
}

How to start an activity from flutter plugin using an API

So I am making a Flutter plugin and I am attempting to run Kotlin code on Android. The problem is, this code runs a method which attempts to start an activity without the FLAG_ACTIVITY_NEW_TASK flag on the intent. The problem with this is that it also does NOT have a way to give it an intent instance as it attempts to instantiate an instance inside the method itself. The method expects to be called from a button or other method that is stored on the UI and called from it. However, since it is called from the onMethodCall method in the Flutter plugin, it does not seem to work. I have attempted many workarounds such as adding a method inside the Activity and running the code inside while calling it from the flutter plugin class. I have also tried using the UIThread and no luck either. Any workarounds?
Note: I have not provided any code due to keeping this API hidden. It should only be known that I am running the code from the onMethodCall event.
Error: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
You can extend your plugin to implement ActivityAware in your plugin class, when you implement it, you get a couple of callbacks that gives you the current activity. Like this :
lateinit activity: Activity? = null
override fun onDetachedFromActivity() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
After that you can just startActivity from the assigned activity variable.
Let me know if you need further help.
As you mentioned, For Flutter plugin any platform-dependent logics should be kept in the subclass of FlutterActivity which was used to show flutter module/screens inside a native module. Now you can launch intent from that subclass without any additional flags.
#note - Subclass of FlutterActvity should be kept in the native module.
class FlutterResponseActivity : FlutterActivity() {
private var methodResult: Result? = null
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return MyApplication.mContext.flutterEngine //Pre-warmed flutter engine
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"startMainActivity" -> {
startMainActivity()
result.success(true)
}
else -> result.notImplemented()
}
}
}
private fun startMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}

How to close activity from class with context usage?

I created another class where I have function of logout:
fun logOut(context: Context) {
context.stopService(Intent(context, CheckNewMessages::class.java))
val intent = Intent(context, LoginScr::class.java)
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP and
Intent.FLAG_ACTIVITY_NEW_TASK and
Intent.FLAG_ACTIVITY_NO_ANIMATION
context.startActivity(intent)
(context as Activity).finish()
}
and as you can see I use this line for finishing activity:
(context as Activity).finish()
But it is still alive and as a result I have two or more same activities at my system. I tried a lot of ways like creating static variable at first activity and using this variable at the second one for closing. But my activity stays alive. I also tried to use lauchmode at manifest and some other ways. Maybe someone knows where I did a mistake?
UPDATE
Two places from which I call logOut(). 1st is interface between RV adapter and fragment:
override fun finish() {
APICallRequests.logOut(context!!)
activity!!.finishAffinity()
}
and 2nd at Interceptor for requests:
private fun updateAccessToken(context: Context) {
val sp = context.getSharedPreferences(Constants.SHARED_PREFS_STORAGE, 0)
synchronized(this) {
val tokensCall = accessTokenApi()
.getNewToken(ReqAccessToken(sp.getString("refresh_token", "")!!))
.execute()
if (tokensCall.isSuccessful) {
} else {
when (tokensCall.code()) {
500 -> {
val thread = object : Thread() {
override fun run() {
Looper.prepare()
Toast.makeText(cont, cont.getString(R.string.server_error_500), Toast.LENGTH_SHORT).show()
Looper.loop()
}
}
thread.start()
}
401 -> {
APICallRequests.logOut(context)
}
}
}
}
}
That's not the way it works. What happens is this. When you do:
context.startActivity(intent)
This doesn't start the new Activity immediately. It just requests that Android starts the new Activity when it gets control back. Then you do this:
(context as Activity).finish()
This call just finishes the current Activity. When you eventually return control to the Android framework, it will launch the new Activity as you requested in your call to startActivity().
If you want your app to exit (ie: all activities finished), you can just do:
(context as Activity).finishAffinity()
This call will finish the current Activity and all other activities in the task that belong to the same app.
NOTE: This only works if all activities in your app share the same affinity, which is the default case.
try to pass Activity instead of Context in inner param fun logOut(activity: Activity), this should help you if you are calling this function from activity. If you calling it from fragment you can use requareActivity.finish()

Kotlin inner class accessing outer class?

How do I call a method in outer class from the inner class?
I can pass it as context, but I can't call methods on it
loginButton.setOnClickListener {
ssoManager.login(emailEditText.text.toString(), passwordEditText.text.toString())
.subscribe(object: Consumer<SSOToken> {
val intent = Intent(this#LoginActiviy, PasscodeActivity::class.java)
this#LoginActiviy.startActivity(intent)
})
I'm not sure what APIs you're using here, I'm gonna assume that your Consumer is java.util.function.Consumer for the sake of the answer.
You are writing code directly in the body of your object, and not inside a function. The first line of creating the Intent only works because you're declaring a property (and not a local variable!).
What you should do instead is implement the appropriate methods of Consumer, and write the code you want to execute inside there:
loginButton.setOnClickListener {
ssoManager.login()
.subscribe(
object : Consumer<SSOToken> {
val foo = "bar" // this is a property of the object
override fun accept(t: SSOToken) {
val intent = Intent(this#LoginActiviy, PasscodeActivity::class.java)
this#LoginActiviy.startActivity(intent)
}
}
)
}

Pass Class Type as Parameter and Check Type Against Another Object in Kotlin

I am attempting to write a method to wait for a type of activity to be present for my Espresso tests. I've seen examples of trying to wait for objects to appear on separate activities than where the test begins, but none have worked for me so far and I'm not keen to modify my production code with idling resources.
In my method, I'd like to get the current activity, and then check if the activity is the specific type of class that I'm wanting. I'm basically modifying some code I found here to work with Kotlin and a generic class type. Unfortunately, I don't know how to either format the argument that is being passed in (currently Class<out Activity>) or I'm improperly using it in my if statement. What is written below doesn't compile.
Any pointers on how to pass my desired activity type as a parameter to the waitForActivity method and how to check against it?
fun waitForActivity(activityType: Class<out Activity>, timeout: Int = 10): Boolean {
val endTime = System.currentTimeMillis() + (timeout * 1000)
do {
val currentActivity = getActivityInstance()
// THIS LINE IS MY ISSUE **********************************************
if(currentActivity != null && currentActivity::class.java is activityType)
return true
// ********************************************************************
SystemClock.sleep(100)
} while (System.currentTimeMillis() < endTime)
return false
}
private fun getActivityInstance(): Activity? {
val activity = arrayOfNulls<Activity>(1)
InstrumentationRegistry.getInstrumentation().runOnMainSync {
val currentActivity: Activity?
val resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(Stage.RESUMED)
if (resumedActivities.iterator().hasNext()) {
currentActivity = resumedActivities.iterator().next() as Activity
activity[0] = currentActivity
}
}
return activity[0]
}
You can replace the entire line using Class.isInstance.
if (activityType.isInstance(currentActivity)) { ... }
In Kotlin, is requires a class name (at compile time, you can't use a String either) - instead of a Class instance. Class.isInstance is used to perform the same check using a Class instance.

Categories

Resources