In Kotlin, will a Handler thread wait another thread finish? - android

I want to record sensor value in every 5ms
But I don't know whether the values are correct if I capture values in other thread. e.g. x is the last value while y and z are previous value
So I write this program to test, if value is wrong, Log.v("abc not equal", "a:$a b:$b c:$c") will be called
But the result is no problem, a b c are always equal
Should I trust this result? Why a b c never be different? (e.g. a:10 b:9 c:9), is the Hander waits SensorEventListener finish?
class MainActivity : AppCompatActivity() {
var a=0
var b=0
var c=0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
sensorManager.registerListener(sensorListener, sensor, SensorManager.SENSOR_DELAY_NORMAL)
val handler = Handler()
val run = object : Runnable{
override fun run() {
if(!(a == b && b == c)){
Log.v("abc not equal", "a:$a b:$b c:$c")
}
handler.postDelayed(this,5)
}
}
handler.postDelayed(run,5)
}
private val sensorListener = object: SensorEventListener {
override fun onAccuracyChanged(p0: Sensor?, p1: Int) {}
override fun onSensorChanged(event: SensorEvent?) {
if(event != null){
val xRaw = event.values[0]
val yRaw = event.values[1]
val zRaw = event.values[2]
if(a>100){
a=0
b=0
c=0
}
a+=1
slow()
b+=1
slow()
c+=1
}
}
}
fun slow(){
var x = 0
while (x < 50000) {
x++
if(x>10) {
var y = Random().nextInt(x - 1)
}
}
}
}

"registerListener()" method has an additional parameter that defines the Handler to be used. You'r not using that additional parameter so the Listener will run all its callback methods ("onSensorChanged()", etc..) in the MainThread/UiThread. Even your "handler" (and the Runnable object) variable is running in the Main/UiThread, so there isn't any problem. Problems could happen if your "registerListener()" method OR/AND the "handler" variable use different Threads.

Related

MainActivity View appears only after calculations

I am new to Android Development... Sorry for asking something so trivial. I don't know how to google this problem.
So let me explain:
I am doing the Android Development Course from Google and in the exercise you have a TextView that is repeatedly changed by an easy mathematical function 4 times after setContentViewis called. Between the changes Thread.sleep(1000) is called to delay the changes.
Expected behavior:
The Main Activity starts and you can see the TextView changing 4 times.
What actually happens:
The App start is delayed for as long as the calculations are set and then afterwards it will display the Main Activity with only the very last calculated result. In this case I would wait 4 seconds (with Thread.sleep(1000) being called 4 times) until the App is completely up and then you only see the result 60 because of 60 / 1.
This is the code:
private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
division()
}
private fun division() {
val numerator = 60
var denominator = 4
repeat(4) {
Thread.sleep(1000)
Log.v(TAG, "${numerator / denominator}")
val view: TextView = findViewById(R.id.division_textview)
view.setText("${numerator / denominator}")
denominator--
}
}
Thanks in advance. I hope someone knows why Google is suggesting this code, but it does not work on my machine.
You need to make a delay by Timer instead of Thread.sleep()
For training you can try something like this.
private val timerHandler = Handler(Looper.getMainLooper())
private val timerRunnable = Runnable {
denominator--
if(demominator != 0) {
//UI changes here
decrementTimer()
}
}
private fun decrementTimer() {
timerHandler.postDelayed(timerRunnable, DELAY_TIME)
}
If you need to start first run immediately use timerRunnable.run() in other case call decrementTimer().
Also would be helpful to control handler with removeCallbacks function when activity goes to background
You can wait in another thread so you don't block the main thread,
try this code it should work correctly, the code inside the lifecycleScope.launch will be moved to another thread so it will not block the UI:
private const val TAG = "MainActivity"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
division()
}
private fun division() {
val numerator = 60
var denominator = 4
lifecycleScope.launch {
repeat(4) {
delay(1000)
Log.v(TAG, "${numerator / denominator}")
val view: TextView = findViewById(R.id.division_textview)
view.setText("${numerator / denominator}")
denominator--
}
}
}
Note: You need to be sure that you add the lifecycle dependency in you app gradle
dependencies {
...
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.1'
...
}
Thanks for your answers everyone. Each of them helped me searching for the right topics. I liked the Handler Class the most and after some further searching, I came up with this solution:
private fun division() {
val numerator = 60
var denominator = 4
val handler = Handler(Looper.getMainLooper())
val divRunnable = object: Runnable {
override fun run() {
if (denominator != 0) {
Log.v(TAG, "${numerator / denominator}")
val view: TextView = findViewById(R.id.division_textview)
view.text = "${numerator / denominator}"
denominator--
handler.postDelayed(this, 1000)
}
}
}
handler.post(divRunnable)
}
This is working exactly as I wanted it to work.

Is it possible to debounce onClick callback right in a listener?

I have a listener for zooming in and out mapview:
class ZoomMapListener(
mapView: MapView,
private val zoom: Zoom,
) : View.OnClickListener {
private val localMapView = WeakReference(mapView)
private var clickCount = 0
override fun onClick(view: View?) {
clickCount++
}
fun moveCamera() {
val mapView = localMapView.get()
mapView?.let {
var cameraPosition = it.map.cameraPosition
val zoom = if (zoom == IN) {
cameraPosition.zoom + (1.0f * clickCount)
} else {
cameraPosition.zoom - (1.0f * clickCount)
}
cameraPosition = CameraPosition(
cameraPosition.target,
zoom,
cameraPosition.azimuth,
cameraPosition.tilt,
)
clickCount = 0
it.map.move(cameraPosition, Animation(Animation.Type.SMOOTH, 0.5f), null)
}
}
}
enum class Zoom {
IN,
OUT
}
In order if user clicks on button several times I've decided to use debounce operator from another answer(https://stackoverflow.com/a/60234167/13236614), so if there are five clicks, for example, camera makes fivefold increase in one operation.
The extension function:
#FlowPreview
#ExperimentalCoroutinesApi
fun View.setDebouncedListener(
listener: ZoomMapListener,
lifecycleCoroutineScope: LifecycleCoroutineScope,
) {
callbackFlow {
setOnClickListener {
listener.onClick(this#setDebouncedListener)
offer(Unit)
}
awaitClose {
setOnClickListener(null)
}
}
.debounce(500L)
.onEach { listener.moveCamera() }
.launchIn(lifecycleCoroutineScope)
}
And how I use it in my fragment:
zoomInMapButton.setDebouncedListener(ZoomMapListener(mapView, Zoom.IN), lifecycleScope)
I think it all looks kinda bad and I'm doubting because of #FlowPreview annotation, so is there a way to make it right in the custom listener class at least?
Using something with #FlowPreview or #ExperimentalCoroutinesApi is sort of like using a deprecated function, because it's possible it will stop working as expected or be removed in a future version of the library. They are relatively stable, but you'll need to check them each time you update your core Kotlin libraries.
My coroutine-free answer on that other question is more like throttleFirst than debounce, because it doesn't delay the first click.
I think you can directly handle debounce in your ZoomListener class by changing only one line of code! Replace clickCount++ with if (++clickCount == 1) v.postDelayed(::moveCamera, interval).
Disclaimer: I didn't test this.
The strategy here is on the first click to immediately post a delayed call to moveCamera(). If any clicks come in during that delay time, they do not post new delayed calls, because their contribution is accounted for in the clickCount that moveCamera() will use when the delay is over.
I also did some cleanup in moveCamera(), but it's functionally the same. In my opinion, ?.let should not be used for local variables because you can take advantage of smart casting (or early returns) for local variables, so you can keep your code more readable and less nested.
class ZoomMapListener(
mapView: MapView,
private val zoom: Zoom,
private val interval: Long
) : View.OnClickListener {
private val localMapView = WeakReference(mapView)
private var clickCount = 0
override fun onClick(v: View) {
if (++clickCount == 1) v.postDelayed(::moveCamera, interval)
}
fun moveCamera() {
val map = localMapView.get()?.map ?: return
val multiplier = if (zoom == IN) 1f else -1f
val newCameraPosition = CameraPosition.builder(map.cameraPosition)
.zoom(map.cameraPosition.zoom + multiplier * clickCount)
.build()
clickCount = 0
map.move(newCameraPosition, Animation(Animation.Type.SMOOTH, 0.5f), null)
}
}
...so is there a way to make it right in the custom listener class at least?
If I'm not mistaken, you want to make the debounce in the method of onClick, right?
override fun onClick(view: View?) {
// debounce
clickCount++
}
If that, why not use this reference link [it's in the link you provided]
https://stackoverflow.com/a/60193549/11835023

mpAndroid RealTime data plot in LineChart from sensors in Kotlin, any suggestions to speed up the plot?

I am building an Android app in Kotlin and i am really new to this.
My app gets data from a sensor that sends 250 data in one sec (250Hz) and save them in a local file and at the same time send them to a server via HTTP requests.
Also there is the possibility to watch these data in app via a Graph, i choose mpAndroid to plot the data into a linear chart.
I have already everything working 100%, except for the live chart, it works but it has a very slow rendering. It can go up to 3 or 4 minutes of delay since the start of the plotting and i need it as close as possible.
Basically my updateGraph method is triggered by few booleans values. When i click on the button to show the graph the boolean value changes to true and the method that receives data from the sensor starts collecting data into an array.
The updateGraph method is called in the onResume method of the LiveGraph Activity, it gets the array and add the data to the Y axis to plot them and actually it does, but as said with a big delay. I am sure the data are right because on the web server i can see them properly.
This is my main thread:
fun renderChartThread() {
val thread = Thread(Runnable {
runOnUiThread {
renderLineChartOnline()
}
})
thread.start()
}
This is the renderLineChartOnline method:
fun renderLineChartOnline(){
isRendering = true
var mChart: LineChart = graph
yArray.add(Entry(0.toFloat(), 1.20!!.toFloat()))
set1 = LineDataSet(yArray, "Saved Session")
set1.setDrawCircles(false);
set1.setDrawValues(false);
set1.setLineWidth(2f)
val data = LineData(set1)
mChart.setData(data)
mChart.getAxisRight().setEnabled(false);
val xAxis = mChart.xAxis
xAxis.position = XAxis.XAxisPosition.BOTTOM_INSIDE
xAxis.setDrawGridLines(false)
xAxis.granularity = 1f
xAxis.textSize = 8f
xAxis.valueFormatter = IndexAxisValueFormatter(xLabel)
}
This is the onResume method
override fun onResume() {
super.onResume()
if (MainActivity.isLiveView == true) {
var mChart: LineChart = graph
//Getting the data from the device activity
dataPoints = SocketActivity.liveBRTDataPoint
updateLiveDataset(dataPoints, mChart)
}
mHandler.postDelayed(mTimer2, 1000)
}
And as last this is my updateGraph method:
fun updateLiveDataset(var1: ArrayList<Float>, mChart: LineChart) {
var i = 0
mTimer2 = object : Runnable {
override fun run() {
i++
yArray.add(Entry(i.toFloat(), var1[i]!!.toFloat()))
// limit the number of visible entries
mChart.setVisibleXRangeMaximum(750f)
mChart.setVisibleXRangeMinimum(750f)
set1 = LineDataSet(yArray, "Live View")
set1.setDrawCircles(false)
set1.setDrawValues(false)
set1.setLineWidth(2f)
mChart.getAxisRight().setEnabled(false);
data = LineData(set1)
mChart.data = data
mChart.setAutoScaleMinMaxEnabled(true);
mChart.axisLeft.removeAllLimitLines()
mChart.axisLeft.resetAxisMaximum()
mChart.axisLeft.resetAxisMinimum()
mChart.notifyDataSetChanged();
mChart.moveViewToX(var1.size.toFloat())
mChart.invalidate()
mChart.notifyDataSetChanged()
mHandler.postDelayed(mTimer2, 4)
}
}
}
Does anyone have any suggestion on how to speed up this process?
Ok, i found a solution that plot real time data and I would like to post to you the solution, maybe can be helpful to someone else. But this gained another problem, i am missing data in the plot process, off course, because the variable that calls the real time array is not synchronized with the function that receives the data from the device.
This is my updated up code.
import android.content.pm.ActivityInfo
import android.content.res.Configuration
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.WindowManager
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.github.mikephil.charting.charts.LineChart
import com.github.mikephil.charting.components.XAxis
import com.github.mikephil.charting.data.Entry
import com.github.mikephil.charting.data.LineData
import com.github.mikephil.charting.data.LineDataSet
import com.github.mikephil.charting.formatter.IndexAxisValueFormatter
import kotlinx.android.synthetic.main.activity_live_graph.*
import okhttp3.Response
import java.io.File
import java.io.InputStream
class LiveGraph : AppCompatActivity() {
companion object{
var isRendering:Boolean = false
var isEcg:Boolean = false
var isBrt:Boolean = false
}
lateinit var chart: LineChart
var dataPoints = ArrayList<Float>()
var pre_filter = ArrayList<Float>()
var yArray = ArrayList<Entry>()
lateinit var set1: LineDataSet
var xLabel = ArrayList<String>()
var data = LineData()
private val mHandler: Handler = Handler()
private var mTimer2: Runnable? = null
lateinit var file: String
var thread: Thread? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_live_graph)
// lock the current device orientation
val currentOrientation = this.resources.configuration.orientation
if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) {
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
} else {
this.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
// Keep screen awake
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
chart = graph
run(chart)
}
fun renderChartOnline(data: ArrayList<Float>, chart: LineChart) {
var i = 0
if (data.size>0){
for (d in data) {
i +=1
yArray.add(Entry(i.toFloat(), d.toFloat()))
var sec = i/250.toFloat()
val mainHandler = Handler(Looper.getMainLooper())
if (sec > 60){
var min = sec/60.toFloat()
xLabel.add(min.toString()+"min")
} else{
xLabel.add(sec.toString()+"sec")
}
}
}else{
yArray.add(Entry(0.toFloat(), 1.20!!.toFloat()))
}
set1 = LineDataSet(yArray, "Saved Session")
set1.setDrawCircles(false);
set1.setDrawValues(false);
set1.setLineWidth(2f)
val data = LineData(set1)
chart.setData(data)
chart.getAxisRight().setEnabled(false);
val xAxis = chart.xAxis
xAxis.position = XAxis.XAxisPosition.BOTTOM_INSIDE
xAxis.setDrawGridLines(false)
xAxis.granularity = 1f // only intervals of 1 day
xAxis.textSize = 8f
xAxis.valueFormatter = IndexAxisValueFormatter(xLabel)
chart.invalidate()
}
fun run(chart: LineChart){
runOnlineGraph()
val mainHandler = Handler(Looper.getMainLooper())
}
fun runOnlineGraph(){
isRendering = true
feedMultiple()
}
private fun feedMultiple() {
if (thread != null) thread!!.interrupt()
val runnable = Runnable { addEntry() }
thread = Thread(Runnable {
while (true) {
runOnUiThread(runnable)
try {
Thread.sleep(4)
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
})
thread!!.start()
}
override fun onResume() {
super.onResume()
dataPoints = SocketActivity.liveECGDataPoint
renderChartOnline(dataPoints, chart)
}
private fun addEntry() {
/***
* Whit this i get the data saved in the Socket process,
* but this gives me the problem of missing datas received,
* if i use an array the delay increase too much then,
* so i m still looking for a solution to this point
*/
dataPoints = SocketActivity.liveDataForGraph
val data = chart.data
if (data != null) {
var set = data.getDataSetByIndex(0)
// set.addEntry(...); // can be called as well
if (set1 == null) {
data.addDataSet(set)
}
for (i in dataPoints){
data.addEntry(
Entry(
set.entryCount.toFloat(),
i
), 0
)
data.notifyDataChanged()
// let the chart know it's data has changed
chart.notifyDataSetChanged()
// move to the latest entry
chart.moveViewToX(data.entryCount.toFloat())
// limit the number of visible entries
chart.setVisibleXRangeMaximum(750f)
chart.setVisibleXRangeMinimum(750f)
chart.getAxisRight().setEnabled(false);
// move to the latest entry
chart.moveViewToX(data.entryCount.toFloat())
chart.setAutoScaleMinMaxEnabled(true);
chart.axisLeft.removeAllLimitLines()
chart.axisLeft.resetAxisMaximum()
chart.axisLeft.resetAxisMinimum()
chart.notifyDataSetChanged(); // let the chart know it's data changed
chart.invalidate()
}
}
override fun onDestroy() {
super.onDestroy()
mHandler.removeCallbacks(mTimer2);
isRendering = false
}
}

Kotlin: Unresolved Reference for variable from instantiated class

I make an ear training app and want the levels to be customizable. So I have a class with the same function for each of the 12 tones, so imagine setDb, setD, setEb etc.:
class MakeLevel(context: Context) {
fun setC(something: Boolean): Boolean {
var c = something
return c
}
I then instantiate the class in my main activity (FullscreenActivity):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_fullscreen)
makeLevel = MakeLevel(this)
}
companion object {
lateinit var makeLevel: MakeLevel
}
Then in the fragment where the levels are selected, I do this:
override fun onResume() {
super.onResume()
majpentlevelbutton.setOnClickListener { view ->
FullscreenActivity.makeLevel.setC(true)
// [same for setD, setE, setG and setA, and false for all the other notes]
view.findNavController().navigate(R.id.action_levelSelectFragment_to_chromaticFragment)
}
}
Now here comes my problem: I want to access the value of c to determine whether ther sounds and the button for c should be loaded or not, and I can´t find a way to do so. For example, I´d like to use it like this:
if (c == true) {
c_button.visibility = View.VISIBLE
}
else {
c_button.visibility = View.GONE
}
I´ve tried c, makeLevel.c, FullscreenActivity.makeLevel.c and many more. Every time I get an Unresolved reference. So my question is how do I get a reference on the var c?
So far c is only a local variable within the method setC.
If you need the value outside of the method you need to define a property:
class MakeLevel(context: Context) {
var c = initValue
fun setC(something: Boolean){
c = something
}
}
Now you can access this variable with: FullscreenActivity.makeLevel.c
Your problem is that you are trying to access a variable outside of its scope.
class MakeLevel(context: Context) {
private var c = initValue
fun setC(something: Boolean){
c = something
}
fun getC(something: Boolean) {
return c
}
if (getC() == true)
c_button.visibility = View.VISIBLE
else
c_button.visibility = View.GONE
}

Android AsyncTask is not updating Progress Bar

Hello I have a problem with asynctask.I play a song then I update duration to progressbar. But when I play a new song progressbar don't back to 0th position and progressbar is continuing with old value
Here is my code:
class Task(context: Context, progressBar: ProgressBar) : AsyncTask<Int, Int, String>() {
#SuppressLint("StaticFieldLeak")
private var progressBar: ProgressBar? = progressBar
private var count = 0
override fun doInBackground(vararg input: Int?): String {
while (count <= input[0]!!) {
count++
publishProgress(count)
Thread.sleep(1000)
if (isCancelled){
count=0
}
}
return "Task completed"
}
override fun onPreExecute() {
progressBar!!.progress = 0
}
override fun onProgressUpdate(vararg values: Int?) {
progressBar!!.progress = values[0]!!
}
}
when I play song :
override fun onItemClicked(position: Int, song: Song) {
val secondsDuration = song.duration!! / 1000
activity!!.pgbSong.max = secondsDuration
val task = Task(context!!, activity!!.pgbSong)
if (task.status == AsyncTask.Status.RUNNING) {
task.cancel(true)
}
task.execute(song.duration)
}
Well, what to say - you never cancel previous async tasks. Cause you're calling cancel(true) on just created async tasks every time:
val task = Task(context!!, activity!!.pgbSong)
if (task.status == AsyncTask.Status.RUNNING) {
task.cancel(true)
}
task.execute(song.duration)
Instead, you should save previous async task in an object variable (something like this):
private var asyncTask : AsyncTask<*,*,*>? = null
And after in the method call:
override fun onItemClicked(position: Int, song: Song) {
if (asyncTask.status == AsyncTask.Status.RUNNING) {
asyncTask.cancel(true)
}
val secondsDuration = song.duration!! / 1000
activity!!.pgbSong.max = secondsDuration
asyncTask = Task(context!!, activity!!.pgbSong)
asyncTask.execute(song.duration)
}
And, I guess, you should do a return in an AsyncTask when you're checking if it canceled or not.
But please don't use AsyncTask in this manner. Cause it holds links views and activity which can prevent those of being garbage collected and so cause a memory leak.
And please don't use !! with Kotlin. Instead use null check or provide default value if null. Examples:
val int = object?.int ?: 0
val context = activity ?: return
val view = activity?.pgbSong ?: return // or if (view != null)

Categories

Resources