Access context inside scope function - android

I need to access context somehow inside scope function.
How can i do that?
class MainActivity : AppCompatActivity() {
....
private val scanCallback = object: ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
result?.let {
Log.i("__BLEGATT", "Found Ble Device: ${it.device.name ?: "Unnamed"} " +
"addres: ${it.device.address}")
if ( it.device.address == "Thermometer" ) {
if ( isScanning ) {
stopBleScan()
}
it.device.connectGatt(******MainActivity.this*********, false, gattCallback)
}
}
}
}
.....
}
First parameter of connectGatt function needs context
As yon can see my code, i coudlnt write MainActivity.this.

you can get context like this in case of scope in kotlin:
result?.let{
it.device.connectGatt(this#MainActivity, false, gattCallback)
}

Related

How can I passs a method except parameter in Kotlin?

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 ?
}
}

How to get result using registerForActivityResult from within ktor's Routing call running in a non-activity class?

How to get result from another activity (registerForActivity) from with in ktor's Routing API call (eg. /POST) running in a non-activity class?
Background: For an Android app, I run ktor server engine 'netty' in a non-activity class HttpServer.kt. I need to call another app's activity from with in ktor's Routing' POST handler, so I pass 'appCompatActivity' from MainActivity.kt. That's done, just because, I assume, registerForActivityResult() has dependency on UI/life cycle class.
Problem arises when running this as below, as registerForActivityResult() requires to be run earlier (like onCreate() ?), and I don't have such a class in this non-activity class. Moreover, the callback to run when ActivityResult is returned needs to call ktor ApplicationCall's respond which is also a suspend function.
class HttpServer(
private val applicationContext: AppCompatActivity
) {
private val logger = LoggerFactory.getLogger(HttpServer::class.java.simpleName)
private val server = createServer()
private fun ApplicationCall.startSaleActivityForResult() { // <====== *
val activityLauncherCustom =
applicationContext.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK || result.resultCode == Activity.RESULT_CANCELED) {
val transactionResultReturned = result.data
// Handle the returned result properly using transactionResultReturned
GlobalScope.launch {
respond(status = HttpStatusCode.OK, TransactionResponse())
}
}
}
val intent = Intent()
// Ignoring statements to create proper action/data intent
activityLauncherCustom.launch(intent) // <====== *
}
fun start() = server.start()
fun stop() = server.stop(0, 0)
private fun createServer(): NettyApplicationEngine {
return GlobalScope.embeddedServer(Netty) {
install(CallLogging)
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
routing {
route("/") {
post {
call.startSaleActivityForResult() // <====== *
}
}
}
}
}
private fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration>
CoroutineScope.embeddedServer(
factory: ApplicationEngineFactory<TEngine, TConfiguration>,
module: Application.() -> Unit
): TEngine {
val environment = applicationEngineEnvironment {
this.parentCoroutineContext = coroutineContext + parentCoroutineContext
this.log = logger
this.module(module)
connector {
this.port = 8081
}
}
return embeddedServer(factory, environment)
}
}
Above is what I tried, but gives below error. And I don't have onCreate on this non-activity class.
java.lang.IllegalStateException: LifecycleOwner com.youtap.upti.MainActivity#38dcf06 is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
Any suggestions to resolve this problem would be grateful.
Below same above snippet as a screenshot to display helper text on declaration/param types from Android Studio:
And I invoke this server class from onCreate() of MainActivity:
To solve your problem and to hide the complexity you can create an intermediate class for launching activity and waiting for a result to come:
import kotlinx.coroutines.channels.Channel
class Repository(private val activity: MainActivity) {
private val channel = Channel<Int>(1)
suspend fun get(input: String): Int {
activity.activityLauncher.launch(input)
return channel.receive()
}
suspend fun callback(result: Int) {
channel.send(result)
}
}
You can store a reference to a repository and an activity launcher in the MainActivity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
HttpServer(this#MainActivity).also { it.start() }
}
}
val activityLauncher = registerForActivityResult(MySecondActivityContract()) { result ->
GlobalScope.launch {
repository.callback(result!!)
}
}
val repository = Repository(this)
}
My second activity and a contract looks like the following:
class ChildActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_child)
val result = Intent()
result.putExtra("name", 6666)
result.data = Uri.parse("http://mydata")
setResult(Activity.RESULT_OK, result)
finish()
}
}
class MySecondActivityContract : ActivityResultContract<String, Int?>() {
override fun createIntent(context: Context, input: String?): Intent {
return Intent(context, ChildActivity::class.java)
.putExtra("my_input_key", input)
}
override fun parseResult(resultCode: Int, intent: Intent?): Int? = when {
resultCode != Activity.RESULT_OK -> null
else -> intent?.getIntExtra("name", 42)
}
override fun getSynchronousResult(context: Context, input: String?): SynchronousResult<Int?>? {
return if (input.isNullOrEmpty()) SynchronousResult(42) else null
}
}
The most simplest part is routing handler:
routing {
route("/") {
post {
val result = (applicationContext as MainActivity).repository.get("input")
call.respondText { result.toString() }
}
}
}
This solution works but only one request is processed at the same time and it's not robust because Activity may be destroyed before HTTP server or repository objects.

How to assign a property either lazily or normally in a conditional way

I would like to assign one property either lazy or in a "normal way", but the problem is, that my value is always cast to "Any". I cannot use the "by" keyword, when I assign a property conditionally. Here is my current approach
abstract class IWorkerContract(private val isLazy: Boolean = false) {
private val workRequest = if (isLazy) {
// Type mismatch. Required: OneTimeWorkRequest Found: Lazy<OneTimeWorkRequest>
lazy {
OneTimeWorkRequestBuilder<Worker>.build()
}
} else {
OneTimeWorkRequestBuilder<Worker>.build()
}
}
Edit Testing
abstract class IWorkerContract(private val isLazy: Boolean = false) {
private val lazyMgr = ResettableLazyManager()
private val workRequest by if (isLazy) {
// Type 'TypeVariable(<TYPE-PARAMETER-FOR-IF-RESOLVE>)' has no method 'getValue(Test, KProperty<*>)' and thus it cannot serve as a delegate
resettableLazy(lazyMgr) {
OneTimeWorkRequestBuilder<Worker>.build()
}
} else {
OneTimeWorkRequestBuilder<Worker>.build()
}
Lazy Delegate
class ResettableLazy<PROPTYPE>(
private val manager: ResettableLazyManager,
private val init: () -> PROPTYPE,
) : Resettable {
#Volatile
private var lazyHolder = initBlock()
operator fun getValue(thisRef: Any?, property: KProperty<*>): PROPTYPE = lazyHolder.value
override fun reset() {
lazyHolder = initBlock()
}
private fun initBlock(): Lazy<PROPTYPE> = lazy {
manager.register(this)
init()
}
}
fun <PROPTYPE> resettableLazy(
manager: ResettableLazyManager,
init: () -> PROPTYPE,
): ResettableLazy<PROPTYPE> = ResettableLazy(manager, init)
value is always cast to "Any"
Yes, because function lazy { } creates a new instance of Lazy<OneTimeWorkRequest>, not OneTimeWorkRequest, those types are incompatible. I don't understand your requirement exactly, but problem can be solved by providing a custom Lazy implementation, e.g.
class InitializedLazy<T>(override val value: T) : Lazy<T> {
override fun isInitialized(): Boolean = true
}
Usage:
abstract class IWorkerContract(private val isLazy: Boolean = false) {
private val workRequest by if (isLazy) {
lazy { OneTimeWorkRequestBuilder<Worker>().build() }
} else {
InitializedLazy(OneTimeWorkRequestBuilder<Worker>().build())
}
}
You could split it up in 2 separate variables:
abstract class IWorkerContract(private val isLazy: Boolean = false) {
private val lazyWorkRequest by lazy {
OneTimeWorkRequestBuilder<Worker>.build()
}
private val workRequest
get() = when {
isLazy -> lazyWorkRequest
else -> OneTimeWorkRequestBuilder<Worker>.build()
}
}
Because of get(), lazyWorkRequest will not be initialised immediately but only when needed.
But more importantly: why is this behaviour needed, what is the harm of always using lazy?
Also, what is the intended purpose of ResettableLazy? It looks like all you want to have a var and this is the solution to solve the missing getValue() or Type mismatch. Is that correct?
It feels to me your question is too specific, too technical. Could you explain without using Kotlin what kind of behaviour you need?
If you access your property in the constructor, if will be computed at instantiation time.
class Foo(val isLazy: Boolean){
val bar: Int by lazy { computeValue() }
init { if (!isLazy) bar }
}

update() not being called inside Activity which is Observer

My MainActivity implements the Observer class. I also have a class called ObservedObject that extends the Observable class.
Here is my custom Observable , called ObservedObject:
class ObservedObject(var value: Boolean) : Observable() {
init {
value = false
}
fun setVal(vals: Boolean) {
value = vals
setChanged()
notifyObservers()
}
fun printVal() {
Log.i("Value" , "" + value)
}
}
Here is my Application called SpeechApp which contains my ObservedObject (an Observable actually):
class SpeechApp: Application() {
var isDictionaryRead = ObservedObject(false)
override fun onCreate() {
super.onCreate()
wordslist = ArrayList()
Thread {
execute()
}.start()
}
fun execute() {
while (/* Condition */) {
//Log.i("Read" , line)
/*Does Something Here*/
}
isDictionaryRead.setVal(true)
}
}
In my MainActivity, I mainly have a dialog, that should be displayed after I have got the output after Speech Recognition. It will display as long as the value of isDictionaryRead doesn't change to true:
class MainActivity(private val REQ_CODE_SPEECH_INPUT: Int = 100) : AppCompatActivity() , Observer{
override fun update(o: Observable?, arg: Any?) {
(o as ObservedObject).printVal()
dialog.hide()
}
private lateinit var app : SpeechApp
private lateinit var dialog: MaterialDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
dialog = MaterialDialog.Builder(this)
.title("Please Wait")
.content("Loading from the Dictionary")
.progress(true , 0)
.build()
app = application as SpeechApp
app.isDictionaryRead.addObserver(this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_speech, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
val id = item?.itemId
when(id) {
R.id.menu_option_speech -> {
invokeSpeech()
}
}
return super.onOptionsItemSelected(item)
}
private fun invokeSpeech() {
/* Does Something, Works Fine */
try {
startActivityForResult(intent , REQ_CODE_SPEECH_INPUT)
}
catch (ex: ActivityNotFoundException) {
/* Does Something */
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQ_CODE_SPEECH_INPUT -> {
if (resultCode == Activity.RESULT_OK && null != data) {
dialog.show()
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
}
Now the problem is, when the SpeechApp sets the value of isDictionaryRead to true, I expect it to call the MainActivity update() method, wherein I have given the code to hide the dialog. That particular code is not working, and my dialog box doesn't go away. Where am I going wrong?
PS. I've pushed my code to Github now, just in case anyone could help me where I am going wrong.
The only thing I can think of that would cause this problem is that the execute() thread that was started in SpeechApp.onCreate finished execution and called isDictionaryRead.setVal(true) before the activity could call app.isDictionaryRead.addObserver(this). As a result, notifyObservers is called before the activity even starts observing, and as a result it is not notified. Here's my proposed solution: Start the execute thread in the activity's onCreate method after adding it as an observer.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
dialog = MaterialDialog.Builder(this)
.title("Please Wait")
.content("Loading from the Dictionary")
.progress(true , 0)
.build()
app = application as SpeechApp
app.isDictionaryRead.addObserver(this)
app.asyncReadDictionary()
}
Then remove the thread call from SpeechApp.onCreate and use this instead
// in SpeechApp
fun asyncReadDictionary() {
if (!isDictionaryRead.value) {
Thread { execute() }.start()
}
}
private fun execute() {
while (/* Condition */) {
//Log.i("Read" , line)
/*Does Something Here*/
}
isDictionaryRead.value = true
}
Also, reimplement ObservableObject as follows
class ObservedObject : Observable() {
var value: Boolean = false
set(newValue) {
field = newValue
setChanged()
notifyObservers()
}
fun printVal() {
Log.i("Value" , "" + value)
}
}

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