Cannot invoke method channel in another activity - android

In MainActivity.kt, I invoke a method call "goToSecondActivity" to go to second activity. I would like to invoke method channel on the SecondActivity too but it doesn't work. The MethodCallHandler doesn't even run.
I got the MissingPluginException(No implementation found for method updatePosition on channel com.example.ble_poc/map_channel) but I have just implemented it and channel name is the same.
Can anyone tell me what should I do? I am new to Android Native.
MainActivtiy.kt:
class MainActivity: FlutterActivity() {
private val CHANNEL = "com.example.ble_poc/channel"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegister.registerGeneratedPlugins(FlutterEngine(this#MainActivity))
MethodChannel(flutterEngine?.dartExecutor!!, CHANNEL).setMethodCallHandler { call, result ->
if(call.method.equals("goToSecondActivity")){
goToSecondActivity()
} else {
print("Result not implemented in MainActivity")
result.notImplemented()
}
}
}
private fun goToSecondActivity() {
startActivity(Intent(this#MainActivity, SecondActivity::class.java))
}
}
SecondActivity.kt:
class SecondActivity: FlutterActivity() {
private val CHANNEL = "com.example.ble_poc/map_channel"
private lateinit var mapView: MPIMapView
private val venueDataString: String by lazy { readFileContentFromAssets("mappedin-demo-mall.json") }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegister.registerGeneratedPlugins(FlutterEngine(this#SecondActivity))
MethodChannel(flutterEngine?.dartExecutor!!.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
if(call.method.equals("updatePosition")){
println("Update position call is triggered")
val lat = call.argument<Double>("lat")
val long = call.argument<Double>("long")
val accuracy = call.argument<Double>("accuracy") ?: 7.170562529369844
val floorLevel = call.argument<Int>("floor") ?: 0
updatePosition(lat!!, long!!, accuracy, floorLevel)
} else {
print("Result not implemented in SecondActivity")
result.notImplemented()
}
}
...
main.dart
static const CHANNEL = "com.example.ble_poc/channel";
static const MAP_CHANNEL = "com.example.ble_poc/map_channel";
static const platform = MethodChannel(CHANNEL);
static const map_platform = MethodChannel(MAP_CHANNEL);
void openMap() async {
try {
platform.invokeMethod('goToSecondActivity');
Timer(const Duration(seconds: 4), () {
map_platform.invokeMethod(
"updatePosition",
{
"lat": 43.52023014,
"long": -80.5352595,
},
);
});
} on PlatformException catch (e) {
print(e.message);
}
}
...

Related

Kotlin, jetpack compose: move braintree to separate class from mainactivity: AppCompatActivity(), dropinListener

I want to move the braintree code from mainactivity to another class, but the DropInListener and AppCompatActivity() cause errors when I move them out of the mainactivity.
Part of the move is easy.
I can certainly create a class as such, but the problem is this class needs to inherit AppCompatActivity. When I do that, it creates a bigger project that i am not sure is the best way to do this. Ideally i would just like to instantiate the braintree class in the MainActivity, doesn't work either..
============================ Proposed class that does not work because of no AppCompatActivity=================
class Braintree constructor() : DropInListener {
**private lateinit var dropInClient: DropInClient
private lateinit var braintreeClient: BraintreeClient
private lateinit var dataCollector: DataCollector**
dropInClient = DropInClient(this#MainActivity, ExampleClientTokenProvider(commonRepository))
dropInClient.setListener(this#MainActivity)
braintreeClient = BraintreeClient(this#MainActivity, CLIENT_TOKEN)
dataCollector = DataCollector(braintreeClient)
}
fun loadDropIn() {
val dropInRequest = DropInRequest()
dropInClient.launchDropIn(dropInRequest)
}
// Function to check and request permission.
private fun checkPermission(permission: String, requestCode: Int) {
if (ContextCompat.checkSelfPermission(this#MainActivity, permission) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this#MainActivity, arrayOf(permission), requestCode)
} else {
Toast.makeText(this#MainActivity, "Permission already granted", Toast.LENGTH_SHORT).show()
}
}
private fun collectDeviceData(paymentMethodNonce: String?) {
dataCollector.collectDeviceData(MainActivity.INSTANCE) { deviceData, error ->
val deviceDataObj = Gson().fromJson(deviceData, DeviceData::class.java)
CoroutineScope(Dispatchers.IO).launch {
if (paymentMethodNonce != null) {
commonRepository.checkout(commonRepository.amount,
commonRepository.uuid, paymentMethodNonce, deviceDataObj.correlation_id)
}
}
}
}
override fun onDropInSuccess(dropInResult: DropInResult) {
Log.d("TAG2", "dropin success")
val paymentMethodNonce = dropInResult.paymentMethodNonce?.string
collectDeviceData(paymentMethodNonce)
}
override fun onDropInFailure(error: Exception) {
if (error is UserCanceledException) {
Log.d("TAG2", "dropin user cancelled : ${error.message}")
} else {
Log.d("TAG2", "dropin failure : ${error.message}")
}
}
}
============= Original code and want to move the items in bold out of the MainActivity class to a separate location/file/class/something=======================
#HiltAndroidApp
class MyApp #Inject constructor() : Application() {
init {
INSTANCE = this
}
companion object {
lateinit var INSTANCE: MyApp
private set
val applicationContext: Context
get() {
return INSTANCE.applicationContext
}
}
}
#AndroidEntryPoint
class MainActivity : AppCompatActivity(), DropInListener {
#Inject lateinit var commonRepository: CommonRepository
**private lateinit var dropInClient: DropInClient**
**private lateinit var braintreeClient: BraintreeClient**
**private lateinit var dataCollector: DataCollector**
init {
INSTANCE = this
}
companion object {
lateinit var INSTANCE: MainActivity
private set
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
SnoozeForGoodTheme(false) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
checkPermission(SCHEDULE_EXACT_ALARM, 1)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
checkPermission(USE_EXACT_ALARM, 1)
}
checkPermission(RECEIVE_BOOT_COMPLETED, 1)
checkPermission(INTERNET, 1)
NavHost(
navController = navController, startDestination = SplashDirections.root.destination) {
composable(route = SplashDirections.root.destination) {
Log.d("TAG2", "going splash page")
SplashContentFactory().Content(navController)
}
}
}
}
**dropInClient = DropInClient(this#MainActivity, ExampleClientTokenProvider(commonRepository))
dropInClient.setListener(this#MainActivity)
braintreeClient = BraintreeClient(this#MainActivity, CLIENT_TOKEN)
dataCollector = DataCollector(braintreeClient)
}
fun loadDropIn() {
val dropInRequest = DropInRequest()
dropInClient.launchDropIn(dropInRequest)
}
// Function to check and request permission.
private fun checkPermission(permission: String, requestCode: Int) {
if (ContextCompat.checkSelfPermission(this#MainActivity, permission) == PackageManager.PERMISSION_DENIED) {
ActivityCompat.requestPermissions(this#MainActivity, arrayOf(permission), requestCode)
} else {
Toast.makeText(this#MainActivity, "Permission already granted", Toast.LENGTH_SHORT).show()
}
}
private fun collectDeviceData(paymentMethodNonce: String?) {
dataCollector.collectDeviceData(MainActivity.INSTANCE) { deviceData, error ->
val deviceDataObj = Gson().fromJson(deviceData, DeviceData::class.java)
CoroutineScope(Dispatchers.IO).launch {
if (paymentMethodNonce != null) {
commonRepository.checkout(commonRepository.amount,
commonRepository.uuid, paymentMethodNonce, deviceDataObj.correlation_id)
}
}
}
}
override fun onDropInSuccess(dropInResult: DropInResult) {
Log.d("TAG2", "dropin success")
val paymentMethodNonce = dropInResult.paymentMethodNonce?.string
collectDeviceData(paymentMethodNonce)
}
override fun onDropInFailure(error: Exception) {
if (error is UserCanceledException) {
Log.d("TAG2", "dropin user cancelled : ${error.message}")
} else {
Log.d("TAG2", "dropin failure : ${error.message}")
}
}**
}

How to turn BroadcastReceiver into StreamHandler while writing native plugin?

I've never written android code before. Now I want to write a Flutter plugin that collects Wi-Fi data.
Following this guide and found a video on YouTube but author uses StreamHandler, but I couldn't find a way to implement StreamHandler related code.
Wrote these code so far in the kotlin side, as my understanding I have to use wifiScanReceiver as StreamHandler but it is a BroadcastReceiver. Therefore the dataChannel!!.setStreamHandler(wifiScanReceiver) gives error.
class MainActivity(wifiManager: WifiManager) : FlutterActivity() {
private val METHOD_CHANNEL_NAME = "com.baran.collect_wifi/method"
private val DATA_CHANNEL_NAME = "com.baran.collect_wifi/data"
private var methodChannel : MethodChannel? = null
private lateinit var wifiManager: WifiManager
private var dataChannel : EventChannel? = null
private var eventSink: EventChannel.EventSink? = null
#RequiresApi(Build.VERSION_CODES.M)
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
//Setup channels
setupChannels(this, flutterEngine.dartExecutor.binaryMessenger)
}
override fun onDestroy() {
teardownChannels()
super.onDestroy()
}
private val wifiScanReceiver = object : BroadcastReceiver() {
#RequiresApi(Build.VERSION_CODES.M)
override fun onReceive(context: Context, intent: Intent) {
val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
if (success) {
scanSuccess()
} else {
scanFailure()
}
}
}
private fun scanSuccess() {
}
private fun scanFailure() {
// handle failure: new scan did NOT succeed
// consider using old scan results: these are the OLD results!
val results = wifiManager.scanResults
}
#RequiresApi(Build.VERSION_CODES.M)
private fun setupChannels(context: Context, messenger: BinaryMessenger){
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
context.registerReceiver(wifiScanReceiver, intentFilter)
methodChannel = MethodChannel(messenger, METHOD_CHANNEL_NAME)
methodChannel!!.setMethodCallHandler{
call, _ ->
if(call.method == "startScan"){
val success = wifiManager.startScan()
if (!success) {
// scan failure handling
scanFailure()
}
}
}
dataChannel = EventChannel(messenger, DATA_CHANNEL_NAME)
dataChannel!!.setStreamHandler(wifiScanReceiver)
}
private fun teardownChannels() {
methodChannel!!.setMethodCallHandler(null)
}
}
And this is the flutter code so far
import 'dart:async';
import 'package:flutter/services.dart';
class CollectWifi {
static const _channel = MethodChannel('com.baran.collect_wifi/method');
static const _dataChannel = EventChannel('com.baran.collect_wifi/data');
}
I don't know if I'm going correctly
you can not pass WifiManager in to setStreamHandler
Activity does not take WifiManager as input parameter
I updated some point in your source code as below
class MainActivity : FlutterActivity() {
private val METHOD_CHANNEL_NAME = "com.baran.collect_wifi/method"
private val DATA_CHANNEL_NAME = "com.baran.collect_wifi/data"
private var methodChannel: MethodChannel? = null
private var dataChannel: EventChannel? = null
private var eventSink: EventChannel.EventSink? = null // using send data to flutter layer
val wifiManager = application.getSystemService(Context.WIFI_SERVICE) as WifiManager
val wifiScanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val success = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)
if (success) {
scanSuccess()
} else {
scanFailure()
}
}
}
private fun scanSuccess() {
val results = wifiManager.scanResults
eventSink?.success("Wifi Connected")// send event to flutter layer
}
private fun scanFailure() {
// handle failure: new scan did NOT succeed
// consider using old scan results: these are the OLD results!
val results = wifiManager.scanResults
eventSink?.error("404","Wifi Not connected",null) // send event to flutter layer
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
context.registerReceiver(wifiScanReceiver, intentFilter)
val success = wifiManager.startScan()
if (!success) {
// scan failure handling
scanFailure()
}
}
override fun onDestroy() {
unregisterReceiver(wifiScanReceiver) // remember unRegister to prevent memory leak
super.onDestroy()
}
#RequiresApi(Build.VERSION_CODES.M)
private fun setupChannels(context: Context, messenger: BinaryMessenger){
val wifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
val intentFilter = IntentFilter()
intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)
context.registerReceiver(wifiScanReceiver, intentFilter)
methodChannel = MethodChannel(messenger, METHOD_CHANNEL_NAME)
methodChannel!!.setMethodCallHandler{
call, _ ->
if(call.method == "startScan"){
val success = wifiManager.startScan()
if (!success) {
// scan failure handling
scanFailure()
}
}
}
dataChannel = EventChannel(messenger, DATA_CHANNEL_NAME)
//dataChannel.setStreamHandler(setStreamHandler(wifiScanReceiver)
dataChannel?.setStreamHandler(object :EventChannel.StreamHandler{
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
eventSink = events // object using send data to flutter layer
}
override fun onCancel(arguments: Any?) {
}
})
}
private fun teardownChannels() {
methodChannel!!.setMethodCallHandler(null)
}
}

How to add Flutter MethodChannel to Android Application.class?

How to add Flutter MethodChannel to Android Application.class without creating a flutter plugin?
I can do it in Activity, but somehow I cannot access MethodChannels if I add them in Application.class.
Android:
Logs: MissingPluginException(No implementation found for method getPreferences on channel ...)
class App : FlutterApplication(), PluginRegistry.PluginRegistrantCallback {
override fun onCreate() {
super.onCreate()
setPreferencesChannel()
}
override fun registerWith(reg: PluginRegistry?) {
GeneratedPluginRegistrant.registerWith(reg)
}
private fun setPreferencesChannel() {
val channel = MethodChannel(FlutterEngine(this).dartExecutor.binaryMessenger, applicationContext.packageName + "/preferences")
channel.setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
when (call.method) {
"getPreferences" -> {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
val map = HashMap<String, Any?>()
map["font_size"] = prefs.getInt("font_size_main", 0)
result.success(map)
}
else -> result.notImplemented()
}
}
}}
Flutter:
class PreferencesChannel {
static final _channel =
MethodChannel(BuildConfig.packageName + '/preferences');
PreferencesChannel._();
static Future<dynamic> getPreferences() async {
try {
return _channel.invokeMethod('getPreferences');
} on PlatformException catch (e) {
Logger.logError(e.message, 'PreferencesChannel: getPreferences');
return null;
}
}
}

Flutter Event Channel Stream causes crash of app (Lost connection to device)

I was following along a tutorial described in this article.
The code of the article can be found here: https://github.com/seamusv/event_channel_sample.
I basically do the same only that i use kotlin instead of java.
In native code (MainActivity.kt):
class MainActivity: FlutterActivity() {
private val STREAM_TAG = "alarm.eventchannel.sample/stream";
private var timerSubscription : Disposable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
EventChannel(getFlutterView(), STREAM_TAG).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
Log.w("TAG", "adding listener")
this#MainActivity.timerSubscription = Observable.interval(0, 1, TimeUnit.SECONDS)
.subscribe (
{
Log.w("Test", "Result we just received: $it");
events.success(1);
}, // OnSuccess
{ error -> events.error("STREAM", "Error in processing observable", error); }, // OnError
{ println("Complete"); } // OnCompletion
)
}
override fun onCancel(arguments: Any?) {
Log.w("TAG", "adding listener")
if (this#MainActivity.timerSubscription != null) {
this#MainActivity.timerSubscription?.dispose()
this#MainActivity.timerSubscription = null
}
}
}
)
}
}
In my main.dart i do the following:
int _timer = 0;
StreamSubscription _timerSubscription = null;
void _enableTimer() {
if (_timerSubscription == null) {
_timerSubscription = stream.receiveBroadcastStream().listen(_updateTimer);
}
}
void _disableTimer() {
if (_timerSubscription != null) {
_timerSubscription.cancel();
_timerSubscription = null;
}
}
void _updateTimer(timer) {
debugPrint("Timer $timer");
setState(() => _timer = timer);
}
In the build function i also create a button which then calls _enableTimer() onPressed.
new FlatButton(
child: const Text('Enable'),
onPressed: _enableTimer,
)
Whenever i now press the button to call _enableTimer() the app crashes and i get the output "Lost connection to device"...
Am i doing something wrong or is this a bug in a newer version of Flutter since the article is from December 2017?
The solution to my problem was basically to start the stream in the main thread:
class MainActivity: FlutterActivity() {
private val CHANNEL = "alarm.flutter.dev/audio"
private val STREAM_TAG = "alarm.eventchannel.sample/stream";
private var timerSubscription : Disposable? = null
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), STREAM_TAG).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink) {
Log.w("TAG", "adding listener")
this#MainActivity.timerSubscription = Observable
.interval(1000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe (
{
Log.w("Test", "Result we just received: $it");
events.success(it);
}, // OnSuccess
{ error -> events.error("STREAM", "Error in processing observable", error); }, // OnError
{ println("Complete"); } // OnCompletion
)
}
override fun onCancel(arguments: Any?) {
Log.w("TAG", "adding listener")
if (this#MainActivity.timerSubscription != null) {
this#MainActivity.timerSubscription?.dispose()
this#MainActivity.timerSubscription = null
}
}
}
)
}

Android NsdManager not able to discover services

I'm running into a problem with Androids NsdManager when following their tutorial Using Network Service Discovery.
I have a few zeroconf/bonjour hardware devices on my network. From my mac I can discover all of them as expected from my terminal with the following
dns-sd -Z _my-mesh._tcp.
From my Android app's first run I can flawlessly discover these services using NsdManager. However if I restart the application and try again none of the services are found. onDiscoveryStarted gets called successfully but then nothing else after. While waiting I can confirm from my mac that the services are still successfully there.
I can then turn on my Zeroconf app (on Android) and it will show the services like my mac. When I return to my app I see it immediately receive all the callbacks I expected previously. So I believe something is wrong with my approach, however I'm not sure what. Below is the code I use to discover and resolve services. The view is a giant textview (in a scroll view) I keep writing text to for debugging easier.
import android.annotation.SuppressLint
import android.content.Context
import android.net.nsd.NsdManager
import android.net.nsd.NsdServiceInfo
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
class MainActivity : AppCompatActivity(),
NsdManager.DiscoveryListener {
private var nsdManager: NsdManager? = null
private var text: TextView? = null
private var isResolving = false
private val services = ArrayList<ServiceWrapper>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
this.text = findViewById(R.id.text)
this.nsdManager = application.getSystemService(Context.NSD_SERVICE) as NsdManager
}
override fun onResume() {
super.onResume()
this.nsdManager?.discoverServices("_my-mesh._tcp.", NsdManager.PROTOCOL_DNS_SD, this)
write("Resume Discovering Services")
}
override fun onPause() {
super.onPause()
this.nsdManager?.stopServiceDiscovery(this)
write("Pause Discovering Services")
}
override fun onServiceFound(serviceInfo: NsdServiceInfo?) {
write("onServiceFound(serviceInfo = $serviceInfo))")
if (serviceInfo == null) {
return
}
add(serviceInfo)
}
override fun onStopDiscoveryFailed(serviceType: String?, errorCode: Int) {
write("onStopDiscoveryFailed(serviceType = $serviceType, errorCode = $errorCode)")
}
override fun onStartDiscoveryFailed(serviceType: String?, errorCode: Int) {
write("onStartDiscoveryFailed(serviceType = $serviceType, errorCode = $errorCode)")
}
override fun onDiscoveryStarted(serviceType: String?) {
write("onDiscoveryStarted(serviceType = $serviceType)")
}
override fun onDiscoveryStopped(serviceType: String?) {
write("onDiscoveryStopped(serviceType = $serviceType)")
}
override fun onServiceLost(serviceInfo: NsdServiceInfo?) {
write("onServiceLost(serviceInfo = $serviceInfo)")
}
private fun createResolveListener(): NsdManager.ResolveListener {
return object : NsdManager.ResolveListener {
override fun onResolveFailed(serviceInfo: NsdServiceInfo?, errorCode: Int) {
write("onResolveFailed(serviceInfo = $serviceInfo, errorCode = $errorCode)")
isResolving = false
resolveNext()
}
override fun onServiceResolved(serviceInfo: NsdServiceInfo?) {
write("onServiceResolved(serviceInfo = $serviceInfo)")
if (serviceInfo == null) {
return
}
for (servicewrapper in services) {
if (servicewrapper.serviceInfo.serviceName == serviceInfo.serviceName) {
servicewrapper.resolve(serviceInfo)
}
}
isResolving = false
resolveNext()
}
}
}
#SuppressLint("SetTextI18n")
private fun write(text: String?) {
this.text?.let {
it.post({
it.text = it.text.toString() + "\n" + text + "\n"
})
}
}
fun add(serviceInfo: NsdServiceInfo) {
for (servicewrapper in services) {
if (servicewrapper.serviceInfo.serviceName == serviceInfo.serviceName) {
return
}
}
services.add(ServiceWrapper(serviceInfo))
resolveNext()
}
#Synchronized
fun resolveNext() {
if (isResolving) {
return
}
isResolving = true
for (servicewrapper in services) {
if (servicewrapper.isResolved) {
continue
}
write("resolving")
this.nsdManager?.resolveService(servicewrapper.serviceInfo, createResolveListener())
return
}
isResolving = false
}
inner class ServiceWrapper(var serviceInfo: NsdServiceInfo) {
var isResolved = false
fun resolve(serviceInfo: NsdServiceInfo) {
isResolved = true
this.serviceInfo = serviceInfo
}
}
}
Better late than never. Did not realize other people were having this issue too until now.
What we discovered was some routers were blocking or not correctly forwarding the packets back and forth. Our solution to this was using wire shark to detect what other popular apps were doing to get around the issue. Androids NsdManager has limited customizability so it required manually transmitting the packet over a MulticastSocket.
interface NsdDiscovery {
suspend fun startDiscovery()
suspend fun stopDiscovery()
fun setListener(listener: Listener?)
fun isDiscovering(): Boolean
interface Listener {
fun onServiceFound(ip:String, local:String)
fun onServiceLost(event: ServiceEvent)
}
}
#Singleton
class ManualNsdDiscovery #Inject constructor()
: NsdDiscovery {
//region Fields
private val isDiscovering = AtomicBoolean(false)
private var socketManager: SocketManager? = null
private var listener: WeakReference<NsdDiscovery.Listener> = WeakReference<NsdDiscovery.Listener>(null)
//endregion
//region NsdDiscovery
override suspend fun startDiscovery() = withContext(Dispatchers.IO) {
if (isDiscovering()) return#withContext
this#ManualNsdDiscovery.isDiscovering.set(true)
val socketManager = SocketManager()
socketManager.start()
this#ManualNsdDiscovery.socketManager = socketManager
}
override suspend fun stopDiscovery() = withContext(Dispatchers.IO) {
if (!isDiscovering()) return#withContext
this#ManualNsdDiscovery.socketManager?.stop()
this#ManualNsdDiscovery.socketManager = null
this#ManualNsdDiscovery.isDiscovering.set(false)
}
override fun setListener(listener: NsdDiscovery.Listener?) {
this.listener = WeakReference<NsdDiscovery.Listener>(listener)
}
#Synchronized
override fun isDiscovering(): Boolean {
return this.isDiscovering.get()
}
//endregion
private inner class SocketManager {
//region Fields
private val group = InetAddress.getByName("224.0.0.251")
?: throw IllegalStateException("Can't setup group")
private val incomingNsd = IncomingNsd()
private val outgoingNsd = OutgoingNsd()
//endregion
//region Constructors
//endregion
//region Methods
suspend fun start() {
this.incomingNsd.startListening()
this.outgoingNsd.send()
}
fun stop() {
this.incomingNsd.stopListening()
}
//endregion
private inner class OutgoingNsd {
//region Fields
private val socketMutex = Mutex()
private var socket = MulticastSocket(5353)
suspend fun setUpSocket() {
this.socketMutex.withLock {
try {
this.socket = MulticastSocket(5353)
this.socket.reuseAddress = true
this.socket.joinGroup(group)
} catch (e: SocketException) {
return
}
}
}
suspend fun tearDownSocket() {
this.socketMutex.withLock {
this#OutgoingNsd.socket.close()
}
}
//ugly code but here is the packet
private val bytes = byteArrayOf(171.toByte(), 205.toByte(), 1.toByte(), 32.toByte(),
0.toByte(), 1.toByte(), 0.toByte(), 0.toByte(),
0.toByte(), 0.toByte(), 0.toByte(), 0.toByte(),
9.toByte(), 95.toByte(), 101.toByte(), 118.toByte(),
97.toByte(), 45.toByte(), 109.toByte(), 101.toByte(),
115.toByte(), 104.toByte(), 4.toByte(), 95.toByte(),
116.toByte(), 99.toByte(), 112.toByte(), 5.toByte(),
108.toByte(), 111.toByte(), 99.toByte(), 97.toByte(),
108.toByte(), 0.toByte(), 0.toByte(), 12.toByte(),
0.toByte(), 1.toByte())
private val outPacket = DatagramPacket(bytes,
bytes.size,
this#SocketManager.group,
5353)
//endregion
//region Methods
#Synchronized
suspend fun send() {
withContext(Dispatchers.Default) {
setUpSocket()
try {
this#OutgoingNsd.socket.send(this#OutgoingNsd.outPacket)
delay(1500L)
tearDownSocket()
} catch (e: Exception) {
}
}
}
//endregion
}
private inner class IncomingNsd {
//region Fields
private val isRunning = AtomicBoolean(false)
private var socket = MulticastSocket(5353)
//endregion
//region Any
fun setUpSocket() {
try {
this.socket = MulticastSocket(5353)
this.socket.reuseAddress = true
this.socket.joinGroup(group)
} catch (e: SocketException) {
} catch (e: BindException) {
}
}
fun run() {
GlobalScope.launch(Dispatchers.Default) {
setUpSocket()
try {
while (this#IncomingNsd.isRunning.get()) {
val bytes = ByteArray(4096)
val inPacket = DatagramPacket(bytes, bytes.size)
this#IncomingNsd.socket.receive(inPacket)
val incoming = DNSIncoming(inPacket)
for (answer in incoming.allAnswers) {
if (answer.key.contains("_my_mesh._tcp")) {
this#ManualNsdDiscovery.listener.get()?.onServiceFound(answer.recordSource.hostAddress, answer.name)
return#launch
}
}
}
this#IncomingNsd.socket.close()
} catch (e: Exception) {
}
}
}
//endregion
//region Methods
#Synchronized
fun startListening() {
if (this.isRunning.get()) {
return
}
this.isRunning.set(true)
run()
}
#Synchronized
fun stopListening() {
if (!this.isRunning.get()) {
return
}
this.isRunning.set(false)
}
//endregion
}
}
}

Categories

Resources