How can I passs a method except parameter in Kotlin? - android

I hope to pass a method which implement MediaRecorder.OnInfoListener.
At present I use Code A to pass the method which implement MediaRecorder.OnInfoListener.
Is there any other way to do it? maybe just like Code B.
Code A
class HomeViewModel(...) : AndroidViewModel(mApplication) {
fun startRecord() {
startRecord(savedfilename){
mr, what, extra ->
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
stopRecord()
}
}
}
}
class RecordService : Service() {
fun startRecord(filename:String, mOnInfoListener: MediaRecorder.OnInfoListener){
mRecorder = MediaRecorder()
mRecorder?.let {
with(it) {
setOnInfoListener(mOnInfoListener)
}
}
}
}
Code B
class HomeViewModel(...) : AndroidViewModel(mApplication) {
{
//I save mOnInfoListener to memory ?
val mOnInfoListener = object: MediaRecorder.OnInfoListener{
mr, what, extra ->
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
stopRecord()
}
}
fun startRecord() {
startRecord(savedfilename)
}
}
class RecordService : Service() {
fun startRecord(filename:String){
mRecorder = MediaRecorder()
mRecorder?.let {
with(it) {
setOnInfoListener(mOnInfoListener) // I get mOnInfoListener from memory ?
}
}
}
}

The Code A looks good in terms of passing a lambda as parameter. But I would make a couple of improvements.
I would encapsulate the work with MediaRecorder in one class, e.g. RecordService, HomeViewModel doesn't need to know anything about what tools RecordService uses to make records. And if you need to react on recorder's events you can create an additional interface for that. The sample code with all interfaces and events will look like the following:
sealed interface Event
object MaxDurationReachedEvent : Event
//... create another events if need
interface RecordServiceEventListener {
fun onRecordEvent(event: Event)
}
interface RecordService {
fun startRecord(filename: String)
fun registerEventListener(listener: RecordServiceEventListener)
fun unregisterEventListener(listener: RecordServiceEventListener)
}
class RecordServiceImpl : Service(), RecordService {
private var recorder: MediaRecorder? = null
private var eventListeners: MutableList<RecordServiceEventListener> = mutableListOf()
override fun startRecord(filename: String) {
recorder = MediaRecorder()
recorder?.setOnInfoListener { mr, what, extra ->
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
eventListeners.forEach { it.onRecordEvent(MaxDurationReachedEvent) }
}
}
}
override fun registerEventListener(listener: RecordServiceEventListener) {
eventListeners.add(listener)
}
override fun unregisterEventListener(listener: RecordServiceEventListener) {
eventListeners.remove(listener)
}
}
class HomeViewModel(private val recordService: RecordService) : ViewModel(), RecordServiceEventListener {
init {
recordService.registerEventListener(this)
}
override fun onCleared() {
recordService.unregisterEventListener(this)
}
fun startRecord() {
recordService.startRecord(savedFilename)
}
override fun onRecordEvent(event: Event) {
when (event) {
MaxDurationReachedEvent -> { ... /* Do something */ }
}
}
}
It is better to work with interfaces rather than with concrete implementations. Also I wouldn't suggest to use m prefix when naming variables, it is a style of Java language, not Kotlin.
P.S. Flow can be used to emit events of a recorder instead of RecordServiceEventListener, but this is another story :)

Here you go:
class HomeViewModel(...) : AndroidViewModel(mApplication) {
//I save mOnInfoListener to memory ?
val mOnInfoListener = object : MediaRecorder.OnInfoListener {
override fun onInfo(mr: MediaRecorder, what: Int, extra: Int) {
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
stopRecord()
}
}
}
fun startRecord() {
startRecord(savedfilename)
}
}
class RecordService(private val mOnInfoListener: MediaRecorder.OnInfoListener) : Service() {
fun startRecord(filename: String) {
val mRecorder = MediaRecorder()
mRecorder.setOnInfoListener(mOnInfoListener) // I get mOnInfoListener from memory ?
}
}

Related

How do I notify Workmanager task completed to Service?

My Worker(for API call) starts from Service and I want to completion event send into Service class.
What should be best approach?
Calling from service:
PeriodicWorkRequest request = new PeriodicWorkRequest
.Builder(Worker.class, Constants.REPEAT_INTERVAL, TimeUnit.MINUTES)
.addTag(TAG)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build())
.build();
WorkManager.getInstance(getApplicationContext()).enqueue(request);
Calling from WorkManager:
override fun doWork(): Result {
// API call
return Result.success()
}
Okay so what I would do is I would create common object for both Worker and Service class and utilize Observer pattern. This WorkObserver object would behave as a proxy between Service and Worker. Using Koin for example, it would look something like that:
class MyWorker: Worker(), KoinComponent {
private val workObserver: WorkObserver by inject()
override fun doWork(): Result {
val result = api.call().execute()
if(result.isSuccessful) {
workObserver.notifySuccess()
return Result.success()
} else {
workObserver.notifyError()
return Result.failure()
}
}
}
class MyService: Service(), KoinComponent {
private val workObserver: WorkObserver by inject()
override fun onCreate() {
workObserver.setOnResultListener { result ->
if(result) {
//do something
} else {
// do something else
}
}
override fun onDestroy() {
workObserver.setOnResultListener(null)
}
}
class WorkObserver {
private var onResultListener: ((Result) -> Unit)? = null
fun setOnResultListener(listener: ((Result) -> Unit)?) {
this.onResultListener = listener
}
fun notifySuccess() {
this.onResultListener?.invoke(true)
}
fun notifyError() {
this.onResultListener?.invoke(false)
}
}
Of course you can use other DI tools for that, you can have a list of listeners and remove particular ones, you can pass any other object through the listener in WorkObserver with the payload you need. I just created a simple boolean passing
For that simple case however if you don't want to use DI, simple Object would do the work. However when your codebase grows and you are dealing with multithreading issues, or even accessing this object in other parts of the application it may lead to problems. I am using it only to pass information between objects, I don't recommend using it for storing data etc:
class MyWorker: Worker() {
override fun doWork(): Result {
val result = api.call().execute()
if(result.isSuccessful) {
WorkObserver.notifySuccess()
return Result.success()
} else {
WorkObserver.notifyError()
return Result.failure()
}
}
}
class MyService: Service() {
override fun onCreate() {
WorkObserver.setOnResultListener { result ->
if(result) {
//do something
} else {
// do something else
}
}
override fun onDestroy() {
WorkObserver.setOnResultListener(null)
}
}
object WorkObserver {
private var onResultListener: ((Result) -> Unit)? = null
fun setOnResultListener(listener: ((Result) -> Unit)?) {
this.onResultListener = listener
}
fun notifySuccess() {
this.onResultListener?.invoke(true)
}
fun notifyError() {
this.onResultListener?.invoke(false)
}
}

ViewModel does not trigger observer of mutablelivedata

I have the following ViewModel class -
class VerifyOtpViewModel : ViewModel() {
private var existingUserProfileData: MutableLiveData<TwoVerteUsers.TwoVerteUser>? = null
fun checkInfoForAuthenticatedUser(authorization: String, user: String) {
ProfileNetworking.getUsersProfiles(authorization, GetUserProfilesBodyModel(listOf(user)), object : ProfileNetworking.OnGetUserProfilesListener {
override fun onSuccess(model: TwoVerteUsers) {
existingUserProfileData?.value = model[0]
}
override fun onError(reason: String) {
Log.d("existingProfile", reason)
}
})
}
fun getExistingUserProfileData(): LiveData<TwoVerteUsers.TwoVerteUser>? {
if (existingUserProfileData == null) return null
return existingUserProfileData as LiveData<TwoVerteUsers.TwoVerteUser>
}
}
and the following observer -
private fun initViewModel() {
verifyOtpViewModel = ViewModelProvider(this).get(VerifyOtpViewModel::class.java)
verifyOtpViewModel.getExistingUserProfileData()?.observe(this, Observer {
if (it != null)
Log.d("existingProfile", it.username)
})
}
For some reason the observe is never triggered even after the MutableLiveData object is being given a value
Tried to search for a solution here at stackoverflow but nothing helped
what am I missing?
refactor your code to this, and you should be good to go:
class VerifyOtpViewModel : ViewModel() {
private val _existingUserProfileData = MutableLiveData<TwoVerteUsers.TwoVerteUser>()
val existingUserProfileData: LiveData<TwoVerteUsers.TwoVerteUser>
get() = _existingUserProfileData
fun checkInfoForAuthenticatedUser(authorization: String, user: String) {
ProfileNetworking.getUsersProfiles(
authorization,
GetUserProfilesBodyModel(listOf(user)),
object : ProfileNetworking.OnGetUserProfilesListener {
override fun onSuccess(model: TwoVerteUsers) {
existingUserProfileData.value = model[0]
}
override fun onError(reason: String) {
Log.d("existingProfile", reason)
}
})
}
}
And observing:
verifyOtpViewModel.existingUserProfileData.observe(this, Observer {
.....
})

How to use the object value in another class in Kotlin and Microsoft Oauth 2.0

I want to use the below object in another class:
private var mSingleAccountApp: ISingleAccountPublicClientApplication? = null
PublicClientApplication.createSingleAccountPublicClientApplication(
this,
R.raw.auth_config_single_account,
object : IPublicClientApplication.ISingleAccountApplicationCreatedListener {
override fun onCreated(application: ISingleAccountPublicClientApplication) {
mSingleAccountApp = application
// loadAccount()
}
override fun onError(exception: MsalException) {
//txt_log.text = exception.toString()
}
}
)
I need to call the below method from another class
fun performOperationOnSignOut() {
mSingleAccountApp!!.signOut(object : ISingleAccountPublicClientApplication.SignOutCallback {
override fun onSignOut() {
}
override fun onError(exception: MsalException) {
//displayError(exception)
}
})
}
I tried to call but mSingleAccountApp always throws NullPointerException
How can I pass or use the mSingleAccountApp variable in another class?
ISingleAccountPublicClientApplication this is an interface
This is the simplest example I could come up with:
//class with one property
class Foo(val name : String){
}
class Bar(){
constructor(foo: Foo) : this() {
//access the property of the foo object
println(foo.name)
}
}
fun main() {
val foo = Foo("John Doe")
//pass foo-object in the constructor
val bar = Bar(foo)
}
see also: https://kotlinlang.org/docs/reference/properties.html
class CustomClass(var listener: ListenerAppImp){
private var mSingleAccountApp: ISingleAccountPublicClientApplication? = null
PublicClientApplication.createSingleAccountPublicClientApplication(
this,
R.raw.auth_config_single_account,
object : IPublicClientApplication.ISingleAccountApplicationCreatedListener {
override fun onCreated(application: ISingleAccountPublicClientApplication) {
mSingleAccountApp = application
listener.getAppValue(mSingleAccountApp) // todo mSingleAccountApp Value through interface
// loadAccount()
}
override fun onError(exception: MsalException) {
//txt_log.text = exception.toString()
}
}
)
}
// todo for example
interface ListenerAppImp {
fun getAppValue(mSingleAccountApp : ISingleAccountPublicClientApplication)
}
class User {
var customClass = CustomClass(object : ListenerAppImp{
override fun getAppValue(mSingleAccountApp: ISingleAccountPublicClientApplication) {
}
})
}
hope that may help you
In your case just implement interface ISingleAccountPublicClientApplication in another class for your understanding let's keep class name is Another
class Another : ISingleAccountPublicClientApplication {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
.....InitialiseYourInterfaceHere.....
}
override fun signOut(..yourcode...){
.......yourcode.......
}
}

How can I generalize functions on an enum class in Kotlin?

How can I create a class which could be more reusable with enum classes, as I might have few more classes later on? My point is to make it more reusable, flexible and global for other usage.
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object {
private val TAG: String = this::class.java.simpleName
fun fromString(name: String): PaymentMethodType? {
return getEnumFromString(PaymentMethodType::class.java, name)
}
private inline fun <reified T : Enum<T>> getEnumFromString(c: Class<T>?, string: String?): T? {
if (c != null && string != null) {
try {
return enumValueOf<T>(
string.trim()
.toUpperCase(Locale.getDefault()).replace(" ", "_")
)
} catch (e: IllegalArgumentException) {
Log.e(TAG, e.message)
}
}
return null
}
}
}
You can generalize your getEnumFromString function by creating an interface and having your companion object implementing it. An extension on this interface will let you call the function directly on the companion of your enum class.
This will do the trick:
interface EnumWithKey<T : Enum<T>, K> {
val T.key: K
}
/* The reified type parameter lets you call the function without explicitly
* passing the Class-object.
*/
inline fun <reified T : Enum<T>, K> EnumWithKey<T, K>.getByKey(key: K): T? {
return enumValues<T>().find { it.key == key }
}
Now you can create your PaymentMethodType like this:
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object : EnumWithKey<PaymentMethodType, String> {
// Just define what the key is
override val PaymentMethodType.key
get() = type
}
}
And voila, now you can do this:
println(PaymentMethodType.getByKey("Paypal")) // Prints PAYPAL
The EnumWithKey interface can now be reused by just having the companion object of an enum implementing it.
Well? How about this code?
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal"),
VISA("Visa"),
MASTERCARD("MasterCard"),
VISA_DEBIT("VISA Debit"),
LPQ_CREDIT("Lpq Credit");
companion object {
private val TAG: String = PaymentMethodType::class.simpleName
fun fromString(name: String?): PaymentMethodType? {
val maybeType = PaymentMethodType.values().firstOrNull { it.type == name }
if (maybeType == null) {
Log.e(TAG, "No corresponding PaymentMethodType for $name")
}
return maybeType
}
}
}
Just made getEnumFromString method simpler like this way.
Moreover, if you want to make your PaymentMethodType more "reusable, flexible and global", add some abstract method onto your PaymentMethodType or consider using Sealed class in this case. We can guess that many payment methods require their own protocols, and implementing it by enum requires an externalised when or if-else branch to do so. For example, the code should be looks like this:
fun paymentProcessor(payment: PaymentMethodType): Boolean {
return when (payment) {
PAYPAL -> { processPaypalPayment() }
VISA -> { processVisaPayment() }
// ...
}
}
which is not bad unless numbers of payment methods are limited but not quite desirable. We can remove this insidious if or when keyword like this way(retaining enum class approach):
enum class PaymentMethodType(val type: String) {
PAYPAL("Paypal") {
override fun processPayment(): Boolean {
TODO("Not implemented.")
}
},
VISA("Visa") {
override fun processPayment(): Boolean {
TODO("Not implemented.")
}
},
// ... more types ...
;
abstract fun processPayment(): Boolean
// ...
}
With either approach, we can eliminate when keyword in paymentProcessor method I demonstrated like this:
fun paymentProcessor(payment: PaymentMethodType): Boolean {
return payment.processPayment()
}
I don't explain sealed class approach since the code is not much different compare to enum class approach in this case. The official document may help.
Hope this helps.
Get all enum values with PaymentMethodType.values(), then use find() to get the one you need:
fun fromString(type: String): PaymentMethodType? = PaymentMethodType.values().find { it.type.toLowerCase() == type.toLowerCase() }

Multiple calls to set LiveData is not observed

I have recently seen a weird issue that is acting as a barrier to my project.
Multiple calls to set the live data value does not invoke the observer in the view.
It seems that only the last value that was set actually invokes the Observer in the view.
Here is the code snippet for a review.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainViewModelImpl::class.java)
viewModel.state().observe(this, Observer {
onStateChange(it!!)
})
viewModel.fetchFirstThree()
}
private fun onStateChange(state: MainViewModel.State) {
when (state) {
is One -> {
show(state.data)
}
is Two -> {
show(state.data)
}
is Three -> {
show(state.data)
}
}
}
private fun show(data: String) {
Log.d("Response", data)
}
}
MainViewModel.kt
abstract class MainViewModel : ViewModel() {
sealed class State {
data class One(val data: String) : State()
data class Two(val data: String) : State()
data class Three(val data: String) : State()
}
abstract fun state(): LiveData<State>
abstract fun fetchFirstThree()
}
MainViewModelImpl.kt
class MainViewModelImpl : MainViewModel() {
private val stateLiveData: MediatorLiveData<State> = MediatorLiveData()
override fun state(): LiveData<State> = stateLiveData
override fun fetchFirstThree() {
stateLiveData.value = State.One("One")
stateLiveData.value = State.Two("Two")
stateLiveData.value = State.Three("Three")
}
}
Expected output:
Response: One
Response: Two
Response: Three
Actual Output:
Response: Three
As per the output above, the Observer is not being called for the first two values.
I did some science, re-implementing LiveData and MutableLiveData to log out some data.
Check the source code here.
setValue value=Test1
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test2
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test3
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
ITEM: Test3
It looks like the observer hasn't reached an active state when you send the initial values.
private void considerNotify(LifecycleBoundObserver observer) {
// <-- Three times it fails here. This means that your observer wasn't ready for any of them.
if (!observer.active) {
return;
}
Once the observer reaches an active state, it sends the last set value.
void activeStateChanged(boolean newActive) {
if (newActive == active) {
return;
}
active = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += active ? 1 : -1;
if (wasInactive && active) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !active) {
onInactive();
}
if (active) {
// <--- At this point you are getting a call to your observer!
dispatchingValue(this);
}
}
I had such issue too.
To resolve it was created custom MutableLiveData, that contains a queue of posted values and will notify observer for each value.
You can use it the same way as usual MutableLiveData.
open class MultipleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
private val values: Queue<T> = LinkedList()
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(this::class.java.name, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, { t: T ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
//call next value processing if have such
if (values.isNotEmpty())
pollValue()
}
})
}
override fun postValue(value: T) {
values.add(value)
pollValue()
}
private fun pollValue() {
value = values.poll()
}
#MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#Suppress("unused")
#MainThread
fun call() {
value = null
}
}
You could use custom LiveData like this:
class ActiveMutableLiveData<T> : MutableLiveData<T>() {
private val values: Queue<T> = LinkedList()
private var isActive: Boolean = false
override fun onActive() {
isActive = true
while (values.isNotEmpty()) {
setValue(values.poll())
}
}
override fun onInactive() {
isActive = false
}
override fun setValue(value: T) {
if (isActive) {
super.setValue(value)
} else {
values.add(value)
}
}
}
FWIW I had the same problem but solved it like this...
I originally had some code similar to this...
private fun updateMonth(month: Int){
updateMonth.value = UpdateMonth(month, getDaysOfMonth(month))
}
updateMonth(1)
updateMonth(2)
updateMonth(3)
I experienced the same problem as described...
But when I made this simple change....
private fun updateMonth(month: Int) {
CoroutineScope(Dispatchers.Main).launch {
updateMonth.value = UpdateMonth(month, getDaysOfMonth(month))
}
}
Presumably, each updateMonth is going onto a different thread now, so all of the updates are observed.
You should call viewModel.fetchFirstThree() after Activity's onStart() method. for example in onResume() method.
Because in LiveData the Observer is wrapped as a LifecycleBoundObserver. The field mActive set to true after onStart().
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
#Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);// return true after onStart()
}
#Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());// after onStart() change mActive to true
}
}
When the observer notify the change it calls considerNotify, before onStart it will return at !observer.mActive
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {// called in onCreate() will return here.
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}

Categories

Resources