How to avoid to hardcoded cast from context to activity? - android

I have MainActivity.kt with passing an activity context to MyObj-class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
MyObj.processing(this)
}
}
MyObj.kt:
class MyObj {
companion object {
fun processing( cx:Context ) {
// -- doesnt work (universal way)
val intent = cx.intent
// -- i have to cast context to activity via hardcoded way (not universal)
val intent = (cx as MainActivity).intent
}
}
}
I would like to have an universal MyObj without a need to cast in a manual way. Is it possible?

Change Context to Activity your function will be like this:
fun processing( ac:Activity ) {
val intent = ac.intent
}

In general, it would be better not to cast anything, but rather pass in the relevant data i.e. Intent in your case:
fun processing (intent: Intent) {
// TODO do your stuff with intent
}

Related

why the different parceable get instantiated when getParcelable is on different one

in android having some parcelable classes
#Parcelize
class ParcelableClassA(private val member: Int) : IAParcelable {
init {
Log.e("+++", "+++ ParcelableClassA::init{}, this: $this")
}
#Parcelize
class ParcelableClassB(private val member: String) : IBParcelable {
init {
Log.e("+++", "+++ ParcelableClassB::init{}, this: $this")
}
in some where it is instantiated and put in a bundle for an activity's creation.
Intent(context, TheActivity::class.java).apply {
val bundle = Bundle()
bundle.putParcelable(EXTRAS_CLASS_A, ParcelableClassA(1))
bundle.putParcelable(EXTRAS_CLASS_B, ParcelableClassB("B")
...
putExtras(bundle)
context.startActivity(this)
}
in the activity class:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val classA = intent.extras?.getParcelable(EXTRAS_CLASS_A) as? IAParcelable
val classB = intent.extras?.getParcelable(EXTRAS_CLASS_B) as? IBParcelable
}
was expect there would be only one printout "+++ ParcelableClassA::init{}, this: $this"
but actually see two instances of the ParcelableClassA are created, one at the
intent.extras?.getParcelable(EXTRAS_CLASS_A)
and the other also happened when the
intent.extras?.getParcelable(EXTRAS_CLASS_B)
is called. So both ParcelableClassA::init{}, and ParcelableClassB::init{} are called twice.
and if there are more data put in the extra, the more ParcelableClassA's instantiation is called, which are just waste.
Is this expected behvior or the way it is done here is not correct?
update:
seems in the BaseBundle's, if in the unparcel() and the mParcelledData is not null it will have this behavior. But not sure why it happens.
if debug it step by step seems mParcelledData is always null and do not have this problem, but if just run it it will show this problem.
void unparcel() {
synchronized (this) {
final Parcel source = mParcelledData;
if (source != null) {
initializeFromParcelLocked(source, /*recycleParcel=*/ true, mParcelledByNative);
} else {
if (DEBUG) {
Log.d(TAG, "unparcel "
+ Integer.toHexString(System.identityHashCode(this))
+ ": no parcelled data");
}
}
}
}
looks like the use of intent.extra? caused the problem.
if change to
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var bundle = intent.extras
val classA = bundle?.getParcelable(EXTRAS_CLASS_A) as? IAParcelable
val classB = bundle?.getParcelable(EXTRAS_CLASS_B) as? IBParcelable
}
seems dont see the dups.

Start activity from other activity in Kotlin. It crashes before loading xml

In Kotlin, in loginButton.setOnClickListener function, the following codes can start ProfileActivity;
val intent=Intent(this#LoginActivity, ProfileActivity::class.java)
startActivity(intent)
finish()
From the ProfileActivity, the following codes can start TestActivity;
val intent=Intent(this#ProfileActivity, TestActivity::class.java)
startActivity(intent)
finish()
However, I want to start the TestActivity from the LoginActivity. So, I updated the codes by changing only the activity name and the codes are below:
val intent=Intent(this#LoginActivity, TestActivity::class.java)
startActivity(intent)
finish()
But, the app crashes before loading activity_test.xml. Why ?
The class in the ProfileActivity.kt is;
class profileActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_profile)
}
}
The class in the TestActivity.kt is;
class TestActivity : AppCompatActivity() {
private val QUANT = false
private val LABEL_PATH = "labels.txt"
private val INPUT_SIZE = 224
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
var strResultUri: String? = null
strResultUri = savedInstanceState.getString(strResultUri)
} else {
setContentView(R.layout.activity_test)
textViewResult = findViewById(R.id.textViewResult)
textViewResult?.setMovementMethod(ScrollingMovementMethod())
}
}
}
var strResultUri: String? = null
strResultUri = savedInstanceState.getString(strResultUri)
What exactly you do here? Passing null inside savedInstanceState.getString() method?
Also, what do you mean by changing only the name? You mean you just changed the context in the following code?
val intent=Intent(this#LoginActivity, TestActivity::class.java)
startActivity(intent)
finish()
That code would work inside login activity for sure.
Also, is that a typing mistake. profileActivity. That's camel case (Not an accepted convention), and you call ProfileActivity::class.java. This shouldn't work. If it's just a typo, ignore it.
Most probabely your these lines cause issue:
var strResultUri: String? = null
strResultUri = savedInstanceState.getString(strResultUri)
Because you are passing strResultUri null value as parameter to getString method
It should be like this:
strResultUri = savedInstanceState.getString("yourKey")

Activity Results API returned data is null

I am trying out the new Activity Results API by trying to return a parcelable dataesque class from a child activity. Using Alpha4 of the library.
I have setup the Intent with a custom contract 'AddAttendeeContract' as per my understanding of the docs. It compiles and runs and as far as I can see the correct methods are being called but the data is just null.
What might I be missing?
class MainActivity : AppCompatActivity() {
...
override fun onCreate(savedInstanceState: Bundle?) {
... //boilerplate setup nonsense
fab.setOnClickListener {
addAttendee()
}
}
private val addAttendee = registerForActivityResult(AddAttendeeContract()) { attendee: AttendeeData? ->
println("Attendee") // this does not print out
println(attendee) // this does not either
}
}
And the contract
class AddAttendeeContract : ActivityResultContract<Void?, AttendeeData?>() {
override fun createIntent(
context: Context,
input: Void?
): Intent =
Intent(context, AddAttendeeActivity::class.java)
override fun parseResult(
resultCode: Int,
intent: Intent?
): AttendeeData? = when {
resultCode != Activity.RESULT_OK -> null
else -> intent?.getParcelableExtra<AttendeeData>("attendee")
}
}
Finally is the invocation in the child activity class.
class AddAttendeeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
... //boilerplate
add.setOnClickListener { //button on a form
val name: String = view.name.text.toString().trim()
val rate: Double = view.rate.text.toString().trim().toDouble()
val number: Int = view.number.text.toString().trim().toInt()
val intent = Intent(this, MainActivity::class.java).apply {
putExtra("attendee", AttendeeData(name=name, rate=rate, number=number))
}
setResult(Activity.RESULT_OK, intent)
startActivity(intent)
}
}
}
Any insights as to what is going on?
This is solved. The problem was that the second activity was startign a new intent, rather than finishing and returning to the old one.
In the second/child activity had to change the line:
startActivity(intent)
to
finish() and things all worked as expected.

How implement a LiveData Singleton

I need to pass a Bitmap between activities without write the image in the internal/external memory.
An Intent can't carry that size so the best option that I found is to use a Singleton Bitmap or extend Livedata and use it as singleton.
(I'm not that good with architecture so if you have a better solution...)
I'm trying to implement the LiveData option since the livedata observer will be useful and I'm following the
official documentation:
class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
private val stockManager: StockManager = StockManager(symbol)
private val listener = { price: BigDecimal ->
value = price
}
override fun onActive() {
stockManager.requestPriceUpdates(listener)
}
override fun onInactive() {
stockManager.removeUpdates(listener)
}
companion object {
private lateinit var sInstance: StockLiveData
#MainThread
fun get(symbol: String): StockLiveData {
sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
return sInstance
}
}
}
But I really don't understand the logic:
What's the listener will be used for?
What's the class StockManager?
If I need it only for a Bitmap do I need to use onActive() and onInactive() too?
I couldn't find a different implementation example anywhere, how can I implement that only for a Bitmap?
------------ UPDATE for the Sanlok Lee answer ----------------
I tried to implement your class BitmapCache example:
In my first activity I attach the observer
companion object {
val myCache = BitmapCache()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.mylayout)
myCache.getCachedBitmap().observe(this, Observer<Bitmap> { selfie: Bitmap? ->
Log.i(TAG, "TRIGGERED")
})
And in my second Activity I set the value like that:
companion object {
val myCache = BitmapCache()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.mylayout)
val bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.android)
Handler().postDelayed({
myCache.cacheBitmap(bitmap)
}, 3000)
}
But the observer is never triggered, are you sure I can create a Live data singleton like that? Thank you!
StockManager in the example is just a random custom class they made just for example purpose.
Just to give you a simpler example that uses a more familiar component, let's imagine that you need to create a custom LiveData that count (and emit the count) the number of user button press while the LiveData is active. It can look like this:
class ButtonClickLiveData(val button: Button) : LiveData<Int>() {
var clickCount = 0
private val listener = { v: View ->
clickCount++
value = clickCount
}
override fun onActive() {
// Set the click listener when LiveData is not active.
button.setOnClickListener(listener)
}
override fun onInactive() {
// Remove the click listener when LiveData is not active.
button.setOnClickListener(null)
}
}
And to explain your question
What's the listener will be used for?
That listener will be attached to the StockManager. When there is any change in StockManager, StockManager class is responsible for invoking this listener, and when the listener is invoked, it will update LiveData value.
What's the class StockManager?
Just an example class.
If I need it only for a Bitmap do I need to use onActive() and onInactive() too?
No. In fact I am guessing you would not need LiveData for transporting large object. Just as you pointed out, a simple singleton cache class is all you need. LiveData would make sense if you have a stream of Bitmap and you want the activities to automatically react to the stream. For example:
class BitmapCache { // This can be a singleton class.
private val bitmapLiveData = MutableLiveData<Bitmap>()
fun cacheBitmap(bmp: Bitmap) {
bitmapLiveData.value = bmp
}
fun getCachedBitmap(): LiveData<Bitmap> = bitmapLiveData as LiveData<Bitmap>
}
Edit:
Here's the singleton version of the class:
object BitmapCache {
private val bitmapLiveData = MutableLiveData<Bitmap>()
fun cacheBitmap(bmp: Bitmap) {
bitmapLiveData.value = bmp
}
fun getCachedBitmap(): LiveData<Bitmap> = bitmapLiveData as LiveData<Bitmap>
}
and it can be used like this:
// Activity A
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.mylayout)
BitmapCache.getCachedBitmap().observe(this, Observer<Bitmap> { selfie: Bitmap? ->
Log.i(TAG, "TRIGGERED")
})
// Activity B
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.mylayout)
val bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.android)
Handler().postDelayed({
BitmapCache.cacheBitmap(bitmap)
}, 3000)
}

Kotlin - passing function as parameter via Intent

I have this function in kotlin extension file to pass method but it doesn't work. Please explain me how it make correctly, I try this:
fun showErrorClientScreen(context: Context, action : () -> Unit) {
val intent = Intent(context, RestClientErrorActivity::class.java)
val bundle = Bundle()
bundle.putSerializable(UPDATE_CLIENT_ERROR, ErrorClientListener { action })
intent.putExtra(UPDATE_CLIENT_ERROR_BUNDLE, bundle)
context.startActivity(intent)
}
use java interface
public interface ErrorClientListener extends Serializable {
void tryAgainFunction();
}
and my activity where i need listen click button and try again send request:
class RestClientErrorActivity: BaseActivity(), View.OnClickListener {
private lateinit var errorClientListener: ErrorClientListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_rest_client_error)
try {
val bundle = intent.getBundleExtra(UPDATE_CLIENT_ERROR_BUNDLE)
errorClientListener = bundle?.getSerializable(UPDATE_CLIENT_ERROR) as ErrorClientListener
} catch (e: Exception) {
e.message
}
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.ib_update -> errorClientListener.tryAgainFunction()
}
}
}
It is quite strange to package interfaces between activities and it is definitely not advisable. One reason why it is maybe not serializing between Activity A and Activity B is because the object was created in Activity A, it is treated as anonymous class creation and Activity A holds the reference to this object, hence preventing it from being serialised. This is good, because you can create references to objects within the interface callback whose reference in turn would be held by class instantiating it. Therefore, garbage collector won't be able to run collections on these objects and free up the space; causing a massive memory leak.
The alternative approach to your problem could be using clean architectures and a Singleton class pattern that is accessible by both activities and instantiated only once by say Activity A:
class SingletonErrorHandler private constructor(){
var isError = false
fun doOnError() {
// do non view related stuff
// like a network call or something
}
companion object {
val instance by lazy { SingletonErrorHandler() }
}
}
in the activity you can define
class ActivityA : AppCompatActivity() {
fun onError() {
SingletonErrorHandler.instance.isError = true
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.a_activity)
}
}
in activity B
class ActivityB : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.b_activity)
val errorHandler = SingletonErrorHandler.instance
if(errorHandler.isError)
errorHandler.doOnError()
}
}
You can write factory method to start the activity like android studio generates factory method for fragment creation.
class RestClientErrorActivity : AppCompatActivity() {
companion object {
private var completion: (() -> Unit)? = null
fun start(context: Context, completion: (() -> Unit)?) {
RestClientErrorActivity.completion = completion
val bundle = Bundle()
intent.putExtra(UPDATE_CLIENT_ERROR_BUNDLE, bundle)
context.startActivity(intent)
}
}
private lateinit var retryButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retryButton = findViewById(R.id.btn_retry)
}
fun onRetryClick(view: View) {
finish()
completion?.invoke()
}
}
Note: completion is not mandatory. so i made that as nullable. if you start activity without using factory method app will not crash.
I had the same problem. As mentioned in HawkPriest's Answer, your object is not serializable, because its an anonymous class. Another way to fix this is to simply implement a non-anonymous class that implements your interface. Here is my code:
Interface
interface MyInterface : Serializable {
fun instruction()
}
Class
class MyClass : MyInterface {
override fun instruction() {
// does something
}
}
Calling Activity
val myObject = MyClass()
val intent = Intent(context, MyActivity::class.java).putExtra("Tag", myObject)
context.startActivity(intent)
Activity
override fun onCreate(savedInstanceState: Bundle?) {
val myObject = intent.getSerializableExtra("Tag") as MyInterface
myObject.instruction()
}
Regarding the "native resources" as mentioned in your comment, you can make your instruction take parameters or pass them to your MyObject.
P.S. The problems I have with the Singleton solution:
Singleton is not eligable for garbage collection, which means it lives on after its not needed anymore. (not 100% sure about that, but that's what I get from this answer)
Using singleton would mean you cant have "multiple different uses" for your activity. If an interface is used, it is to be able to use multiple different implementations of that interface. A singleton wouldn't provide that, without using an interface architecture within your singleton, which would then again render it unnecessary, considering my proposed solution.

Categories

Resources