I am trying to awake flutter method on a received call. I already done the receiving part, but I can't awake flutter method yet.
I tried calling method channel in onReceive() method in MainActivity.kt class, but it gives me error. method channel only seems to work in onCreate() method.
The question is how can I invoke flutter method in onReceive() or is there another way to do it?
MainActivity.kt
import android.Manifest
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity(){
var updateUIReciver: BroadcastReceiver? = null
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
registerReceiver(broadcastReceiver, IntentFilter("Service.to.activity"));
val channel = "my.data"
val methodChannel = MethodChannel(flutterView, channel)
val map: HashMap<String, String> = HashMap()
val permissionCheck: Int = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Permission granted ", Toast.LENGTH_LONG).show();
} else {
//TODO
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 4);
Toast.makeText(this, "Permission not granted ", Toast.LENGTH_LONG).show();
}
methodChannel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result? ->
if (call.method == "callMyFunction") {
methodChannel.invokeMethod("callMyFunction", map)
} else {
}
}
}
var broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Toast.makeText(context, "Incoming call received", Toast.LENGTH_LONG).show()
// I can't call "methodChannel.invokeMethod("callMyFunction", map)" here cause of error.
}
}
}
MyBroadcastReceiver.kt
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.PhoneStateListener
import android.telephony.TelephonyManager
import androidx.core.app.NotificationCompat
import android.app.NotificationManager;
import android.os.Build;
import android.os.IBinder;
import android.widget.Toast
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val telephony = context.getSystemService(Service.TELEPHONY_SERVICE) as TelephonyManager
telephony.listen(object : PhoneStateListener() {
override fun onCallStateChanged(state: Int, incomingNumber: String) {
super.onCallStateChanged(state, incomingNumber)
context.sendBroadcast(Intent("Service.to.activity"))
}
}, PhoneStateListener.LISTEN_CALL_STATE)
}
}
Flutter code
const platform = const MethodChannel('my.data');
Future<void> _receiveFromNative(MethodCall call) async {
try {
if (call.method == "callMyFunction") {
print("Received in flutter");
}
} on PlatformException catch (e) {}
}
platform.setMethodCallHandler(_receiveFromNative);
Basically you can't access methodChannel in BroadcastReceiver directly so you have to make methodChannel in compaion object So,
Add these lines to your MainActivity
companion object {
lateinit var methodChannel: MethodChannel
}
And in onCreate method of MainActivity replace
val methodChannel = MethodChannel(flutterView, channel)
To :
methodChannel = MethodChannel(flutterView, channel)
now you can use MainActivity.methodChannel anywhere in your app.
Related
I want to add some kotlin code on my flutter app. I add onCreate function on project_name/android/app/src/main/kotlin/com/example/app/MainActivity.kt.
This is my full MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import android.os.Environment
import android.net.Uri
import android.content.Intent
class MainActivity: FlutterActivity() {
private val channel = "externalStorage";
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel).setMethodCallHandler { call, result ->
when (call.method) {
"getExternalStorageDirectory" ->
result.success(Environment.getExternalStorageDirectory().toString())
"getExternalStoragePublicDirectory" -> {
val type = call.argument<String>("type")
result.success(Environment.getExternalStoragePublicDirectory(type).toString())
}
else -> result.notImplemented()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!Settings.canDrawOverlays(getApplicationContext())) {
val myIntent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
val uri: Uri = Uri.fromParts("package", getPackageName(), null)
myIntent.setData(uri)
startActivityForResult(myIntent, REQUEST_OVERLAY_PERMISSIONS)
return
}
}
}
then I got this error on my build
'onCreate' overrides nothing
Unresolved reference: Settings
Unresolved reference: Settings
Unresolved reference:
REQUEST_OVERLAY_PERMISSIONS
Do I need to import some code?
Finally, I found the answer. I need to import some code to add onCreate method.
import android.os.Bundle for Bundle parameter. See this link.
import android.provider.Settings because I use Settings.ACTION_MANAGE_OVERLAY_PERMISSION. See this link.
I forgot to add var REQUEST_OVERLAY_PERMISSIONS = 100
And now it works.
i'm practising a bit with kotlin and was testing Room and livedata, my app gets data from a json and the stores it in room, i want to move this network call to its own file and class, but if i do so the observer i set to get the changes don't trigger anymore, any help would be appreciated
here is a snipped of my mainactivity, if more is needed to know what happens please let me know
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.room.Room
import com.optiva.videoplayer.data.*
import com.optiva.videoplayer.network.GetData
import com.optiva.videoplayer.network.Networking
import com.optiva.videoplayer.network.RetrofitConnect
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbcategories = Room.databaseBuilder(applicationContext, CategoriesDatabase::class.java,"categories.db").build()
val dbvideo = Room.databaseBuilder(applicationContext, VideosDatabase::class.java,"videos.db").build()
val retrofitData = RetrofitConnect.retrofitInst?.create(GetData::class.java)
val categoriesList = retrofitData?.getAll()
categoriesList?.enqueue(object: Callback<DataList> {
override fun onResponse(
call: Call<DataList>,
response: Response<DataList>
) {
val test = response?.body()
val cat = test?.categories
val vid = test?.videos
lifecycleScope.launch(Dispatchers.IO) {
if (cat != null) {
for(c in cat){
dbcategories.categoriesDAO().insertAll(CategoriesEntity(c.id,c.title,c.type))
}
}
if (vid != null) {
for(v in vid){
dbvideo.VideosDAO().insertAll(VideosEntity(v.id,v.thumb,v.videoUrl,v.categoryId,v.name))
}
}
}
}
override fun onFailure(call: Call<DataList>, t: Throwable) {
Toast.makeText(applicationContext,"error", Toast.LENGTH_LONG).show()
}
})
val textView: TextView = findViewById(R.id.test) as TextView
dbcategories.categoriesDAO().getALL().observeForever({categories ->
if(categories.size>0){
textView.text= categories[0].title
}
})
dbcategories.categoriesDAO().getALL().observe(this, {categories ->
if(categories.size>0){
textView.text= categories[0].title
}
}
} ```
I created a worker that runs on connecting audio jack but I can't stop the running worker on audio jack disconnection which is triggered by Broadcast Receiver.
I want to keep track of headset usage limit with the help of worker which will only trigger when there is headset connection and will stop the worker whenever the audio jack/headset is detached.
Here is the Broadcast Reciver Code
package com.xanjit.focusly.broadcast_receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.work.*
import com.xanjit.focusly.workers.GetHeadsetUsageTimeWorker
import java.sql.Timestamp
import java.time.Duration
class AudioInputBroadcastReceiver : BroadcastReceiver() {
// lateinit var workManager: WorkManager
// lateinit var workRequest: WorkRequest
#RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
val isConnected = intent!!.getIntExtra("state", 0) === 1
val workManager = WorkManager.getInstance(context!!)
if (isConnected) {
var startTime = System.currentTimeMillis()
Toast.makeText(context,"Connected",Toast.LENGTH_SHORT).show()
val data = Data.Builder()
data.putLong("startTime", startTime)
val workRequest = OneTimeWorkRequest.Builder(
GetHeadsetUsageTimeWorker::class.java,
).setInputData(data.build())
.build()
workManager.enqueue(workRequest)
} else {
Toast.makeText(context,"Disconnected",Toast.LENGTH_SHORT).show()
// val data = Data.Builder()
//
// data.putBoolean("isStopped", true)
// val workRequest = OneTimeWorkRequest.Builder(
// GetHeadsetUsageTimeWorker::class.java,
// ).setInputData(data.build()).build()
workManager.cancelAllWork()
}
}
override fun peekService(myContext: Context?, service: Intent?): IBinder {
return super.peekService(myContext, service)
}
}
Worker Code
package com.xanjit.focusly.workers
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.graphics.BitmapFactory
import android.media.AudioAttributes
import android.net.Uri
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.work.ListenableWorker
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.xanjit.focusly.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.sql.Timestamp
import kotlin.time.seconds
var TAG = "EarGuard"
class GetHeadsetUsageTimeWorker(context: Context, workerParams: WorkerParameters) : Worker(
context,
workerParams,
) {
#RequiresApi(Build.VERSION_CODES.O)
override fun doWork(): Result {
// Log.d("EarGuard", "StartTime Worker")
var startTime = inputData.getLong("startTime", System.currentTimeMillis())
// var stopped = inputData.getBoolean("isStopped", false)
// if (!isStopped and !stopped) {
// Log.d("EarGuard", "StartTime Worker" + startTime)
Log.d(TAG, isStopped.toString())
Thread {
while (!isStopped) {
Thread.sleep(1000)
var usageTime = System.currentTimeMillis()
usageTime -= startTime
Log.d(TAG, usageTime.toString())
if ((usageTime / 1000) == 5L) {
CoroutineScope(Dispatchers.Main).launch(Dispatchers.Main)
{
// Toast.makeText(
// applicationContext,
// "Time limit exceeded", Toast.LENGTH_LONG
// ).show()
val notificationChannel = NotificationChannel(
"123",
"EarGuard",
NotificationManager.IMPORTANCE_HIGH
)
notificationChannel.description =
"You have exceeded the maximum usage limit of listening on headset"
notificationChannel.name = "EarGuard"
notificationChannel.lockscreenVisibility = 1
notificationChannel.shouldShowLights()
notificationChannel.enableVibration(true)
notificationChannel.setSound(
Uri.parse("R.raw.sad"),
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH).build()
)
notificationChannel.shouldVibrate()
var notificationManager =
applicationContext.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(notificationChannel)
notificationManager.notify(
123, NotificationCompat.Builder(
applicationContext, "123"
).setSmallIcon(R.drawable.ic_launcher_foreground)
.setLargeIcon(
BitmapFactory.decodeResource(
applicationContext.resources,
R.drawable.ic_launcher_foreground
)
)
.setSound(
Uri.parse("R.raw.sad")
)
.setContentTitle("EarGuard")
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentText("You have exceeded the maximum usage limit of listening on headset")
.setAutoCancel(false)
.setChannelId("123")
.build()
)
}
break
}
}
}.start()
// }
return Result.success()
}
// #SuppressLint("RestrictedApi")
// override fun isRunInForeground(): Boolean {
// return super.isRunInForeground()
// }
override fun onStopped() {
Log.d(TAG, "Stopped Worker")
super.onStopped()
}
}
MainActivity.kt
package com.xanjit.focusly
import android.content.*
import android.net.Uri
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.material.*
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest
import com.xanjit.focusly.broadcast_receivers.AudioInputBroadcastReceiver
import com.xanjit.focusly.services.CustomService
import com.xanjit.focusly.workers.GetHeadsetUsageTimeWorker
import java.lang.Exception
class MainActivity : ComponentActivity() {
lateinit var receiver: BroadcastReceiver
var TAG = "EarGuard"
lateinit var workManager: WorkManager
lateinit var workRequest: WorkRequest
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
receiver = AudioInputBroadcastReceiver()
setContent {
Surface() {
Scaffold(
topBar = {
TopAppBar() {
}
}
) {
}
}
}
}
override fun onStart() {
super.onStart()
var intentFilter = IntentFilter("android.intent.action.HEADSET_PLUG");
registerReceiver(receiver, intentFilter)
}
}
You could try keep you workRequest.id on your AudioInputBroadcastReceiver and in case of disconnection you can stop this work. I think its a bad practice cancel all running works if you only want to finish your audio work.
cancelAllWork()
Cancels all unfinished work. Use this method with extreme caution! By invoking it, you will potentially affect other modules or libraries in your codebase. It is strongly recommended that you use one of the other cancellation methods at your disposal.
class AudioInputBroadcastReceiver : BroadcastReceiver() {
val workerRequestId: UUID? = null
#RequiresApi(Build.VERSION_CODES.O)
override fun onReceive(context: Context?, intent: Intent?) {
val isConnected = intent!!.getIntExtra("state", 0) === 1
val workManager = WorkManager.getInstance(context!!)
if (isConnected) {
....
val workRequest = OneTimeWorkRequest.Builder(
GetHeadsetUsageTimeWorker::class.java,
).setInputData(data.build()).build()
workerRequestId = workRequest.id
workManager.enqueue(workRequest)
} else {
Toast.makeText(context,"Disconnected",Toast.LENGTH_SHORT).show()
workerRequestId?.let { workManager.cancelWorkById(it) }
}
}
...
I used the Foreground Service code from Floating Window in localazy.com. My foreground Service didn't send correct codes to the onStartCommand() after I change the package prefix name that they in given in their code in the putExtra Method it always sends null instead of sending the EXIT and NOTE command. Actually, what is the use of the package prefix in the key name? Why did this problem happen?.
package com.krithik.floatingnote.service
import android.app.*
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.util.Log
import android.widget.Toast
import androidx.core.app.NotificationCompat
import com.krithik.floatingnote.R
const val INTENT_COMMAND = "com.localazy.quicknote.COMMAND"
const val INTENT_COMMAND_EXIT = "EXIT"
const val INTENT_COMMAND_NOTE = "NOTE"
private const val NOTIFICATION_CHANNEL_GENERAL = "note_general"
private const val CODE_FOREGROUND_SERVICE = 1
private const val CODE_EXIT_INTENT = 2
private const val CODE_NOTE_INTENT = 3
class FloatingService : Service() {
override fun onBind(intent: Intent?): IBinder? = null
/**
* Remove the foreground notification and stop the service.
*/
private fun stopService() {
stopForeground(true)
stopSelf()
}
/**
* Create and show the foreground notification.
*/
private fun showNotification() {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val exitIntent = Intent(this, FloatingService::class.java).apply {
putExtra(INTENT_COMMAND, INTENT_COMMAND_EXIT)
}
val noteIntent = Intent(this, FloatingService::class.java).apply {
putExtra(INTENT_COMMAND, INTENT_COMMAND_NOTE)
}
val exitPendingIntent = PendingIntent.getService(
this, CODE_EXIT_INTENT, exitIntent, 0
)
val notePendingIntent = PendingIntent.getService(
this, CODE_NOTE_INTENT, noteIntent, 0
)
// From Android O, it's necessary to create a notification channel first.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
with(
NotificationChannel(
NOTIFICATION_CHANNEL_GENERAL,
getString(R.string.notification_channel_general),
NotificationManager.IMPORTANCE_DEFAULT
)
) {
lockscreenVisibility = Notification.VISIBILITY_PUBLIC
manager.createNotificationChannel(this)
}
} catch (ignored: Exception) {
// Ignore exception.
}
}
with(
NotificationCompat.Builder(
this,
NOTIFICATION_CHANNEL_GENERAL
)
) {
setContentTitle(getString(R.string.app_name))
setContentText(getString(R.string.notification_text))
setAutoCancel(false)
setOngoing(true)
setSmallIcon(R.drawable.ic_baseline_note_24)
setContentIntent(notePendingIntent)
addAction(
NotificationCompat.Action(
0,
getString(R.string.notification_exit),
exitPendingIntent
)
)
startForeground(CODE_FOREGROUND_SERVICE, build())
}
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
val command = intent.getStringExtra(INTENT_COMMAND)
Log.i("ServiceCommand", command.toString())
// Exit the service if we receive the EXIT command.
// START_NOT_STICKY is important here, we don't want
// the service to be relaunched.
if (command == INTENT_COMMAND_EXIT) {
stopService()
return START_NOT_STICKY
}
// Be sure to show the notification first for all commands.
// Don't worry, repeated calls have no effects.
showNotification()
// Show the floating window for adding a new note.
if (command == INTENT_COMMAND_NOTE) {
Toast.makeText(
this,
"Floating window to be added",
Toast.LENGTH_SHORT
).show()
}
return START_STICKY
}
}
package com.krithik.floatingnote
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.krithik.floatingnote.database.Note
import com.krithik.floatingnote.database.NoteDatabase
import com.krithik.floatingnote.database.NoteRepository
import com.krithik.floatingnote.databinding.ActivityMainBinding
import com.krithik.floatingnote.service.FloatingService
import com.krithik.floatingnote.service.INTENT_COMMAND
import com.krithik.floatingnote.viewModel.NoteViewModel
import com.krithik.floatingnote.viewModel.NoteViewModelFactory
import com.krithik.floatingnote.viewModel.RecyclerViewAdapter
class MainActivity : AppCompatActivity(), RecyclerViewAdapter.RowClickListener {
private lateinit var binding: ActivityMainBinding
private lateinit var noteViewModel: NoteViewModel
private lateinit var adapter: RecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startFloatingService()
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val dao = NoteDatabase.getInstance(application).noteDao
val repository = NoteRepository(dao)
val factory = NoteViewModelFactory(repository)
noteViewModel = ViewModelProvider(this, factory).get(NoteViewModel::class.java)
binding.noteViewModel = noteViewModel
binding.lifecycleOwner = this
noteViewModel.message.observe(this, Observer {
it.getContentIfNotHandled()?.let {
Toast.makeText(this, it, Toast.LENGTH_LONG).show()
}
})
initRecyclerView()
noteViewModel.noteList.observe(this, Observer {
adapter.submitList(it)
})
}
private fun initRecyclerView() {
binding.noteRecyclerView.layoutManager = LinearLayoutManager(this)
adapter = RecyclerViewAdapter(this)
binding.noteRecyclerView.adapter = adapter
}
override fun onDeleteNote(note: Note) {
noteViewModel.deleteNote(note)
}
private fun Context.startFloatingService(command: String = "") {
val intent = Intent(this, FloatingService::class.java)
if (command.isNotBlank()) intent.putExtra(INTENT_COMMAND, command)
Log.i("Command", INTENT_COMMAND + command)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
this.startForegroundService(intent)
} else {
this.startService(intent)
}
}
}
```
I have the following code of activity:
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.Handler
import android.support.v7.app.AppCompatActivity
import com.mikhailovskii.androidacademytask8.R
import com.mikhailovskii.androidacademytask8.data.service.ProgressIntentService
import kotlinx.android.synthetic.main.activity_main.*
class ProgressActivity : AppCompatActivity() {
private val receiver = ActivityBroadcastReceiver(Handler())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tv_progress.text="0"
val intentFilter = IntentFilter(ProgressIntentService.INTENT_ACTION)
registerReceiver(receiver, intentFilter)
btn_intent_service.setOnClickListener {
val intent = Intent(this, ProgressIntentService::class.java)
startService(intent)
}
btn_service.setOnClickListener {
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver)
}
//FIXME handler1 seems strange, but passing to constructor doesn't work
class ActivityBroadcastReceiver (handler1: Handler) : BroadcastReceiver() {
private val handler = handler1
override fun onReceive(context: Context?, intent: Intent?) {
val progress = intent?.getIntExtra(ProgressIntentService.EXTRA_KEY_OUT, 0)
handler.post { tv_progress.text = progress }
}
}
}
I need to change the text in tv_progress from BroadcastReceiver. For this purpose I added Handler here. But name of tv is highlighted in red and studio writes that
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch
So, what's the reason of this problem and how can I solve it? Thanks in advance!
Please check the name on broadcast in below line :
val intentFilter = IntentFilter(ProgressIntentService.INTENT_ACTION)