How to access a function that's nested inside an override method - android

private fun openTrailer() {
val trailerDialog = Dialog(this)
val trailerDialogBinding = DialogTrailerBinding.inflate(layoutInflater)
trailerDialog.setContentView(trailerDialogBinding.root)
youtubePLayerInit = object : YouTubePlayer.OnInitializedListener {
override fun onInitializationSuccess(
p0: YouTubePlayer.Provider?,
youtubePlayer: YouTubePlayer?,
p2: Boolean
) {
fun loadTrailer2(videoId: String) {
youtubePlayer?.loadVideo(videoId)
}
}
override fun onInitializationFailure(
p0: YouTubePlayer.Provider?,
p1: YouTubeInitializationResult?,
) {
toast("la")
}
}
if (!isInitialized) {
trailerDialogBinding.vvMovieTrailer.initialize(youtubeApiKey, youtubePLayerInit)
isInitialized = true
} else {
Log.e("initializerStatus", "Already Initialized")
}
trailerDialog.show()
}
How do I access access the loadTrailer2 function in the main code? Load Trailer is the function inside the object of the YoutubePlayer.OnInitializedListener. I'm trying to accesss it outside the open trailer function, aka in the onCreate method

Well, as it is right now, loadTrailer2 is a local function declared inside onInitializationSuccess - it only exists while that function is running, so you can't access it from outside that scope.
You could move it into the object itself, but since it relies on the YoutubePlayer object being passed in, how would you call it? Do you have access to that player in onCreate?
If you did have access to it (e.g. when the initialisation first succeeds, you store it in a ViewModel or something, and use that next time you create a fragment) you still have an issue: the type of youtubePlayerInit is YouTubePlayer.OnInitializedListener, and you're adding another method to your object, one that isn't part of that YouTubePlayer.OnInitializedListener interface.
What that means, is nothing actually knows that method exists. If you create that anonymous object inside a function, the compiler sees it as a special type that contains that method, so you can access it directly:
interface Thing {
fun onSomeEvent(code: Int)
}
fun main() {
doSetup()
}
fun doSetup() {
val myThing = object : Thing {
override fun onSomeEvent(code: Int) {
println("Event code: $code")
}
fun coolFunction() {
println("Secret function accessed")
}
}
// calling it inside the scope the object was declared in, so the function
// is visible here
myThing.coolFunction()
}
>> Secret function accessed
but outside of that scope, it just looks like the type declared when creating the object (Thing in this example):
interface Thing {
fun onSomeEvent(code: Int)
}
fun main() {
val myThing = doSetup()
// this only knows it's a Thing, which doesn't have this method
myThing.coolFunction()
}
fun doSetup(): Thing {
val myThing = object : Thing {
override fun onSomeEvent(code: Int) {
println("Event code: $code")
}
fun coolFunction() {
println("Secret function accessed")
}
}
return myThing
}
>> Unresolved reference: coolFunction
If you wanted to access that function, you'd have to e.g. declare a CoolFunctionHaver interface with that function, and either declare myThing as that type, or cast it if you really have to

Related

Generic way to handle all type of responses in Fragment

I have created a Generic Fragment class to handle all type of responses from server. I want to do some sort of DataTableProvider<*> to hanle any type of response.
How could I achieve this.
class TestFragmentActivity : AppCompatActivity(), DataTableProvider<Any> {
protected val mTabPatientsFragment = TabPatientsFragment()
protected val mTabObservationsFragment = TabObservationsFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_fragment)
replaceFragment()
}
private fun replaceFragment(){
supportFragmentManager.beginTransaction().replace(R.id.frame_container,
mTabPatientsFragment).commit()
}
override fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<Any> {
val dataTableId = mTableFragment.dataTableId
if (dataTableId.equals("observations"))
return mTabObservationsFragment
else return mTabPatientsFragment
}
override fun getDataTableConfig(mTableFragment: DataTableFragment<Any>): DataTableConfig {
val dataTableId = mTableFragment.dataTableId
val config = DataTableConfig()
config.noRecordCell = R.layout.cell_no_record
config.showSearch = false
config.showAddButton = false
if (dataTableId.equals("observations"))
{
config.cellResourceId = R.layout.home_observation_cell
} else config.cellResourceId = R.layout.home_patient_cell
return config
}
}
getDataTableListener callback in above fragment has error type mismatch required DataTableListener found TabObservationFragment
TabObservationFragment
class TabObservationFragment : AppBaseFragment(),DataTableListener<Observation>
TabPatientFragment
class TabPatientFragment : AppBaseFragment(),DataTableListener<Patient>
How could I set it to work for all type of responses.
I tried DataTableListener<*> but could not achieve
The error states
projections are not allowed for immediate arguments of a supertype
How could I use DataTableProvider<*> to work for all type of responses
Edit
I have couple of fragment with fragmentViewpager inside TestFragmentActivity .
I have got a structure that helps to implement pagination ,search and implement everything in a fragment. But according to that structure DataTableProvider must be handle in activity and basis of tableId I updated callback of getDataTableListener and getDataTableListener
The above callback should return some type of
Is there a way to achieve callback like below
override fun getDataTableConfig(mTableFragment: DataTableFragment<*>?): DataTableConfig?
override fun getDataTableListener(mTableFragment: DataTableFragment<*>?): DataTableListener<*>?
Quick answer, use "out" modifier:
fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<out Any>
Long answer:
What you are looking for is variance, which can you read about in official kotlin docs:
https://kotlinlang.org/docs/reference/generics.html
Because for example List interface looks like this:
public interface List<out E>
You can do assigement like this:
val list: List<Any> = listOf(1,2,3)
But it is not possible to do:
val mutableList : MutableList<Any> = listOf(1,2,3)
Because MutableList doesn't have "out" modifier. It makes sense, because MutableList can be changed, to MutableList you could add for example String, but it already points to List.
In your example you can use "out" modifier, if, and only if, your DataTableListener doesn't use generic type as input. For example:
interface DataTableListener<T>{
fun assignValue(t:T)
fun getValue():T
}
With interface like above, you still could use "out" modifier in your function, but you won't be able to execute "assignValue" function.
Whole example:
class Patient
class Observation
class DataTableFragment<T>
interface DataTableListener<T> {
fun assignValue(t: T)
fun getValue(): T
}
class TabObservationFragment : DataTableListener<Observation> {
override fun getValue(): Observation {
TODO("Not yet implemented")
}
override fun assignValue(t: Observation) {
TODO("Not yet implemented")
}
}
class TabPatientFragment : DataTableListener<Patient> {
override fun getValue(): Patient {
}
override fun assignValue(t: Patient) {
TODO("Not yet implemented")
}
}
val mTabObservationsFragment = TabObservationFragment()
val mTabPatientsFragment = TabPatientFragment()
fun getDataTableListener(mTableFragment: DataTableFragment<Any>): DataTableListener<out Any> {
val test = "observations"
if (test == "observations")
return mTabObservationsFragment
else return mTabPatientsFragment
}
fun getIt() {
val listener = getDataTableListener(DataTableFragment())
listener.assignValue("test")
}

Converting a callback to a suspendCoroutine, when the callback is a field of class

I want to convert some 3rd-party API based on callbacks to simple suspend functions that are easy to use. Here's a simplified example of mine implementation.
class LibraryWrapper {
private lateinit var onFooFired: (Boolean) -> Unit
private val libraryCallback = object : LibraryCallback {
override fun foo(result: Boolean) {
onFooFired(result)
}
}
private val library = Library(libraryCallback)
suspend fun bar(): Boolean {
return suspendCoroutine { performingBar: Continuation<Boolean> ->
onFooFired = {fooResult -> performingBar.resume(fooResult) }
library.bar()
}
}
}
But this solution sometimes works and sometimes not. There is such an issue with this lambda field, that sometimes it initializes correctly, but sometimes the exception is thrown that "lateinit property onFooFired is not initialized".
It's very strange because I do initialize it before run library.bar() and foo of LibraryCallback is called only after library.bar() was called.
first of all, I think it is not a good approach to use "lateinit var" when you don't control the initialization of a field. Use lateinit only when you have the promise of initialization.
Try to use nullable field implementation like
private var onFooFired: ((Boolean) -> Unit)? = null
and in callback :
private val libraryCallback = object : LibraryCallback {
override fun foo(result: Boolean) {
onFooFired?.invoke(result)
}
}
In this case, until you do not have an implementation of "onFooFired" lambda, it does not invoke

Return value from override function

I'm not sure what I'm looking to do is even possible. If it is, it's new to me.
here's a basic outline of what I'm trying to accomplish:
class MyClass : SomeInterface {
fun makeSomethingHappen() {
methodInInterfaceThatReturnsValueBelow()
}
override fun iDidSomething(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
override fun iDidSomethingElse(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
override fun onFailure(result: Value) {
//give this value back to the original caller of makeSomethingHappen()
}
}
Explanation:
We're using a required SDK that has about 15 overrides. I call into this class to call a function in the SDK. That function is going to call one of the override functions when it's done.
Is there a way (live data, flows, anything) to have whoever called makeSomethingHappen() receive the value from any of the override methods?
This is a basic example of reactive programming. You wait for an event that a producer/observable emits, it's like when you declare a click listener on a button.
You can't return the value in makeSomethingHappen(), but you can create a listener and the observer implements that listener to get the value.
typealias MyListener = (Value) -> Unit
class MyClass : SomeInterface {
private var _listeners: List<MyListener> = mutableListOf()
fun addListener(listener: MyListener) {
_listeners.add(listener)
}
fun makeSomethingHappen() {
methodInInterfaceThatReturnsValueBelow()
}
override fun iDidSomething(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
override fun iDidSomethingElse(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
override fun onFailure(result: Value) {
// Send result Value to the listener implementations
_listeners.forEach { it.invoke(result) }
}
}
The you can get the result implementing MyListener
val myClass = MyClass()
myClass.makeSomethingHappen { value ->
// Here you have the value and you can do whatever
print(value)
}

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.

Kotlin: How to inherit from RxJava Subscriber

I want my NewsListSubscriber to inherit from an RxJava Subscriber which use a generic type but I get a "Type mismatch" error when I call the UseCase execute method. I read many times the generics page from the Kotlin documentation but I can't find the solution.
Here is my UseCase:
abstract class UseCase(private val threadExecutor: IThreadExecutor,
private val postExecutionThread: IPostExecutionThread) {
private var subscription = Subscriptions.empty()
fun execute(UseCaseSubscriber: rx.Subscriber<Any>) {
subscription = buildUseCaseObservable()
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler())
.subscribe(UseCaseSubscriber)
}
protected abstract fun buildUseCaseObservable(): Observable<out Any>
fun unsubscribe() {
if (!subscription.isUnsubscribed) {
subscription.unsubscribe()
}
}
}
And here is how I call it:
override fun loadNewsList() {
getNewsListInteractor.execute(NewsListSubscriber())
}
private inner class NewsListSubscriber : rx.Subscriber<List<NewsModel>>() {
override fun onCompleted() {// TODO}
override fun onError(e: Throwable) {// TODO}
override fun onNext(t: List<NewsModel>) {// TODO}
}
The error is
"Type mismatch. Required: rx.Subscriber. Found: Presenters.NewsListPresenter.NewsListSubscriber"
in the "execute(NewsListSubscriber())" line. I tried playing with the "in" and "out" keywords but I still have the same error.
There is actually a better way to solve this problem. I ran into the same issue and a type cast inside every derived subscriber class was not an option.
Just update the abstract UseCase class with an generic type parameter.
abstract class UseCase<T>(private val threadExecutor: IThreadExecutor,
private val postExecutionThread: IPostExecutionThread) {
private var subscription = Subscriptions.empty()
fun execute(UseCaseSubscriber: rx.Subscriber<T>) {
subscription = buildUseCaseObservable()
.subscribeOn(Schedulers.from(threadExecutor))
.observeOn(postExecutionThread.getScheduler())
.subscribe(UseCaseSubscriber)
}
protected abstract fun buildUseCaseObservable(): Observable<T>
fun unsubscribe() {
if (!subscription.isUnsubscribed) {
subscription.unsubscribe()
}
}
}
When you declare your derived UseCase classes, use your concrete type for the generic parameter when calling the super class.
class ConcreteUseCase(val threadExecutor: IThreadExecutor,
val postExecutionThread: IPostExecutionThread)
: UseCase<ConcreteType>(threadExecutor, postExecutionThread)
Doing so, you can use typed Subscribers in your execute call.
getNewsListInteractor.execute(NewsListSubscriber())
...
private inner class NewsListSubscriber : rx.Subscriber<List<NewsModel() {
override fun onCompleted() {// TODO}
override fun onError(e: Throwable) {// TODO}
override fun onNext(t: List<NewsModel>) {// TODO}
}
I found the solution that is pretty simple actually: my NewsListSubscriber class has to extends from rx.Subscriber<Any> instead of rx.Subscriber<MyWantedClass>. It means I need to cast the received objects to the wanted type.
private inner class NewsListSubscriber : DefaultSubscriber<Any>() {
override fun onCompleted() {}
override fun onError(e: Throwable) {}
override fun onNext(t: Any?) {
val newsList = t as List<News>
...
}
}
In Java the cast is done in background but in Kotlin we need to do it ourself.
I also removed all "in" or "out" keywords in my UseCase class.

Categories

Resources