msal-flutter kotlin.UninitializedPropertyAccessException: lateinit property mainActivity has not been initialized - android

I am using msal-flutter in one of the flutter app.
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.10.0"
Getting following error in Android app.
E/MethodChannel#msal_flutter(28234): Failed to handle method call
E/MethodChannel#msal_flutter(28234): kotlin.UninitializedPropertyAccessException: lateinit property mainActivity has not been initialized
E/MethodChannel#msal_flutter(28234): at com.signify.msal_flutter.MsalFlutterPlugin.initialize(MsalFlutterPlugin.kt:230)
E/MethodChannel#msal_flutter(28234): at com.signify.msal_flutter.MsalFlutterPlugin.onMethodCall(MsalFlutterPlugin.kt:125)
E/MethodChannel#msal_flutter(28234): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:233)
E/MethodChannel#msal_flutter(28234): at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:85)
E/MethodChannel#msal_flutter(28234): at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:692)
E/MethodChannel#msal_flutter(28234): at android.os.MessageQueue.nativePollOnce(Native Method)
E/MethodChannel#msal_flutter(28234): at android.os.MessageQueue.next(MessageQueue.java:335)
E/MethodChannel#msal_flutter(28234): at android.os.Looper.loop(Looper.java:183)
E/MethodChannel#msal_flutter(28234): at android.app.ActivityThread.main(ActivityThread.java:7660)
E/MethodChannel#msal_flutter(28234): at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#msal_flutter(28234): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
E/MethodChannel#msal_flutter(28234): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
I guess method registerWith is not calling.
Following is the code using in MsalFlutterPlugin class:
#Suppress("SpellCheckingInspection")
public class MsalFlutterPlugin: FlutterPlugin, MethodCallHandler {
private lateinit var channel : MethodChannel
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "msal_flutter")
channel.setMethodCallHandler(this);
}
companion object {
lateinit var mainActivity : Activity
lateinit var msalApp: IMultipleAccountPublicClientApplication
fun isClientInitialized() = ::msalApp.isInitialized
#JvmStatic
fun registerWith(registrar: Registrar) {
Log.d("MsalFlutter","Registering plugin")
val channel = MethodChannel(registrar.messenger(), "msal_flutter")
channel.setMethodCallHandler(MsalFlutterPlugin())
mainActivity = registrar.activity()
}
fun getAuthCallback(result: Result) : AuthenticationCallback
{
Log.d("MsalFlutter", "Getting the auth callback object")
return object : AuthenticationCallback
{
override fun onSuccess(authenticationResult : IAuthenticationResult){
Log.d("MsalFlutter", "Authentication successful")
Handler(Looper.getMainLooper()).post {
result.success(authenticationResult.accessToken)
}
}
override fun onError(exception : MsalException)
{
Log.d("MsalFlutter","Error logging in!")
Log.d("MsalFlutter", exception.message)
Handler(Looper.getMainLooper()).post {
result.error("AUTH_ERROR", "Authentication failed", exception.localizedMessage)
}
}
override fun onCancel(){
Log.d("MsalFlutter", "Cancelled")
Handler(Looper.getMainLooper()).post {
result.error("CANCELLED", "User cancelled", null)
}
}
}
}
private fun getApplicationCreatedListener(result: Result) : IPublicClientApplication.ApplicationCreatedListener {
Log.d("MsalFlutter", "Getting the created listener")
return object : IPublicClientApplication.ApplicationCreatedListener
{
override fun onCreated(application: IPublicClientApplication) {
Log.d("MsalFlutter", "Created successfully")
msalApp = application as MultipleAccountPublicClientApplication
result.success(true)
}
override fun onError(exception: MsalException?) {
Log.d("MsalFlutter", "Initialize error")
result.error("INIT_ERROR", "Error initializting client", exception?.localizedMessage)
}
}
}
}
override fun onMethodCall(#NonNull call: MethodCall, #NonNull result: Result) {
val scopesArg : ArrayList<String>? = call.argument("scopes")
val scopes: Array<String>? = scopesArg?.toTypedArray()
val clientId : String? = call.argument("clientID")
val authority : String? = call.argument("authorityURL")
Log.d("A_MsalFlutter","Got scopes: ${scopes.toString()}")
Log.d("A_MsalFlutter","Got cleintId: $clientId")
Log.d("A_MsalFlutter","Got authority: $authority")
when(call.method){
"logout" -> Thread(Runnable{logout(result)}).start()
"init" -> initialize(clientId, authority, result)
"acquireTokenInteractively" -> Thread(Runnable {acquireToken(scopes, result)}).start()
"acquireTokenSilent" -> Thread(Runnable {acquireTokenSilent(scopes, result)}).start()
else -> result.notImplemented()
}
}
private fun acquireToken(scopes : Array<String>?, result: Result)
{
Log.d("MsalFlutter", "acquire token called")
// check if client has been initialized
if(!isClientInitialized()){
Log.d("MsalFlutter","Client has not been initialized")
Handler(Looper.getMainLooper()).post {
result.error("NO_CLIENT", "Client must be initialized before attempting to acquire a token.", null)
}
}
//check scopes
if(scopes == null){
Log.d("MsalFlutter", "no scope")
result.error("NO_SCOPE", "Call must include a scope", null)
return
}
//remove old accounts
while(msalApp.accounts.any()){
Log.d("MsalFlutter","Removing old account")
msalApp.removeAccount(msalApp.accounts.first())
}
//acquire the token
msalApp.acquireToken(mainActivity, scopes, getAuthCallback(result))
}
private fun acquireTokenSilent(scopes : Array?, result: Result)
{
Log.d("MsalFlutter", "Called acquire token silent")
// check if client has been initialized
if(!isClientInitialized()){
Log.d("MsalFlutter","Client has not been initialized")
Handler(Looper.getMainLooper()).post {
result.error("NO_CLIENT", "Client must be initialized before attempting to acquire a token.", null)
}
}
//check the scopes
if(scopes == null){
Log.d("MsalFlutter", "no scope")
Handler(Looper.getMainLooper()).post {
result.error("NO_SCOPE", "Call must include a scope", null)
}
return
}
//ensure accounts exist
if(msalApp.accounts.isEmpty()){
Handler(Looper.getMainLooper()).post {
result.error("NO_ACCOUNT", "No account is available to acquire token silently for", null)
}
return
}
//acquire the token and return the result
val res = msalApp.acquireTokenSilent(scopes, msalApp.accounts[0], msalApp.configuration.defaultAuthority.authorityURL.toString())
Handler(Looper.getMainLooper()).post {
result.success(res.accessToken)
}
}
private fun initialize(clientId: String?, authority: String?, result: Result)
{
Log.d("MsalFlutter", "inside the initialize block")
//ensure clientid provided
if(clientId == null){
Log.d("MsalFlutter","error no clientId")
result.error("NO_CLIENTID", "Call must include a clientId", null)
return
}
//if already initialized, ensure clientid hasn't changed
if(isClientInitialized()){
Log.d("MsalFlutter","Client already initialized.")
if(msalApp.configuration.clientId == clientId)
{
result.success(true)
} else {
result.error("CHANGED_CLIENTID", "Attempting to initialize with multiple clientIds.", null)
}
}
// if authority is set, create client using it, otherwise use default
if(authority != null){
Log.d("MsalFlutter", "Authority not null")
Log.d("MsalFlutter", "Creating with: $clientId - $authority")
PublicClientApplication.create(mainActivity.applicationContext, clientId, authority, getApplicationCreatedListener(result))
}else{
Log.d("MsalFlutter", "Authority null")
PublicClientApplication.create(mainActivity.applicationContext, clientId, getApplicationCreatedListener(result))
}
}
private fun logout(result: Result){
while(msalApp.accounts.any()){
Log.d("MsalFlutter","Removing old account")
msalApp.removeAccount(msalApp.accounts.first())
}
Handler(Looper.getMainLooper()).post {
result.success(true)
}
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
}
msal_auth.dart::
class MsalAuth {
static const MethodChannel _channel =
const MethodChannel('msal_flutter');
//Configuration parameters
String _clientId, _authority, _redirectURI;
MsalAuth(String clientID, {String authorityURL, String redirectURL}){
throw Exception("Direct call is not allowed. Please use PublicClientApplication static method");
}
MsalAuth._create(String clientId, {String authority, String redirectURI}) {
_clientId = clientId;
_authority = authority;
_redirectURI = redirectURI;
}
static Future publicClientApplication(String clientID, {String authorityURL, String redirectURL}) async{
var res = MsalAuth._create(clientID, authority: authorityURL, redirectURI: redirectURL);
await res._initialize();
return res;
}
Future _initialize() async {
var res = <String, dynamic>{'clientID': this._clientId};
//if authority has been set, add it aswell
if (this._authority != null) {
res["authorityURL"] = this._authority;
}
if (this._redirectURI != null) {
res["redirectURI"] = this._redirectURI;
}
try {
await _channel.invokeMethod('init', res);
} on PlatformException catch (e) {
throw _convertException(e);
}
}
/// Acquire a token interactively for the given [scopes]
Future acquireToken(List scopes) async {
var res = <String, dynamic>{'scopes': scopes};
try {
final String token = await _channel.invokeMethod('acquireTokenInteractively', res);
return token;
} on PlatformException catch (e) {
throw _convertException(e);
}
}
/// Acquire a token silently, with no user interaction, for the given [scopes]
Future acquireTokenSilent(List scopes) async {
var res = <String, dynamic>{'scopes': scopes};
try {
final String token =
await _channel.invokeMethod('acquireTokenSilently', res);
return token;
} on PlatformException catch (e) {
throw _convertException(e);
}
}
Future logout() async {
try {
await _channel.invokeMethod('logout', <String, dynamic>{});
} on PlatformException catch (e) {
throw _convertException(e);
}
}
MsalException _convertException(PlatformException e) {
switch (e.code) {
case "CANCELLED":
return MsalUserCancelledException();
case "NO_SCOPE":
return MsalInvalidScopeException();
case "NO_ACCOUNT":
return MsalNoAccountException();
case "NO_CLIENTID":
return MsalInvalidConfigurationException("Client Id not set");
case "INVALID_AUTHORITY":
return MsalInvalidConfigurationException("Invalid authroity set.");
case "CONFIG_ERROR":
return MsalInvalidConfigurationException(
"Invalid configuration, please correct your settings and try again");
case "NO_CLIENT":
return MsalUninitializedException();
case "CHANGED_CLIENTID":
return MsalChangedClientIdException();
case "INIT_ERROR":
return MsalInitializationException();
case "AUTH_ERROR":
default:
return MsalException("Authentication error");
}
}
}

Related

Firebase Auth with Kotlin Flow

I am learning clean architecture and Kotlin Flow. I want to check is user mail exists in the Firebase Auth base. However, when I threw an error to the flow function, app is crash.
CheckUserUseCase.kt
class CheckUserUseCase #Inject constructor(private val repository: SignInRepository) {
operator fun invoke(mail: String): Flow<Status<Boolean, String>> = flow {
emit(Status.Loading(data = null))
try {
repository.isUserExists(mail = mail)
emit(Status.Success(data = true))
} catch (e: Exception) {
emit(Status.Error(message = e.message, data = false))
}
}
}
SignInRepository.kt
interface SignInRepository {
suspend fun isUserExists(mail: String)
}
SignInRepositoryImpl.kt
class SignInRepositoryImpl #Inject constructor(private val firebaseUserActions: FirebaseUserActions) : SignInRepository {
override suspend fun isUserExists(mail: String) {
firebaseUserActions.isUserExists(mail = mail)
}
}
FirebaseAuthentication.kt
class FirebaseAuthentication #Inject constructor(private val auth: FirebaseAuth) : FirebaseUserActions {
override suspend fun isUserExists(mail: String){
auth.fetchSignInMethodsForEmail(mail).addOnCompleteListener { task ->
task.result.signInMethods?.let {
if (it.size != 0) Log.i("App.tag", "True.")
else throw IOException() <-- Crash point.
}
}.addOnFailureListener { e -> e.printStackTrace() }
.await()
}
}
How can I return a state to Kotlin Flow method? Thank you!
Please try the following approach:
override suspend fun isUserExists(mail: String): Status {
return try {
val result = auth.fetchSignInMethodsForEmail(mail).await()
result.signInMethods?.let {
if (it.isNotEmpty()) {
Status.Success(data = true)
} else {
Status.Error(message = "No data", data = false)
}
} ?: Status.Error(message = "No Data", data = false)
} catch (e: Exception) {
Status.Error(message = e.message, data = false)
}
}
In CheckUserUseCase class just emit the result of calling isUserExists():
emit(Status.Loading(data = null))
emit(repository.isUserExists(mail = mail))
Try
it.size != 0 && it.size != null
and
if (task.isSuccessful()) {
[...]
task.result.signInMethods?.let {
[...]
}

How to synchonize executing http requests (kotlin, android)?

I am debugging an application that communicates with an IoT device via http.
In response to commands, the device sends information in xml format.
An application can also receive binary data on a GET request.
In the functionality of the application, filling the RecyclerView from the list and loading images to fill the RecyclerView and executing individual commands to change modes.
The problem is that the device does not have the most powerful processor, and when a large number of http commands are received, the service cannot cope and hangs for a long time until the WiFi channel fails.
I can’t figure out how to organize interaction so that each next command waits for the previous one to complete. The solution is complicated by the fact that populating the RecyclerView, loading images, and executing commands are in different parts of the code, and each is executed asynchronously.
Populating RecyclerView:
private fun initViewModel(filter: String) {
val st = Storage(requireContext())
val cache = "${st.externalCacheDir}/$filter/"
val viewModel = ViewModelProvider(this).get(DeviceListViewModel::class.java)
viewModel.getRecycerListObserver().observe(requireActivity(), Observer<ResponseData> {
if (it != null) {
val media = it.mediaData?.filter { it.mediaData?.fPath!!.contains(filter, false) }
mediaList = arrayListOf()
if (media != null) {
for (i in media.sortedByDescending { it.mediaData?.fTimeCode }) {
i.mediaData?.let { it1 -> mediaList.add(it1) }
}
}
viewModel.recyclerListLiveData = MutableLiveData()
ThumbDownloader(dataAdapter, mediaList, cache, swipeLayout).execute()
} else {
Toast.makeText(activity, "Error in getting data", Toast.LENGTH_SHORT).show()
}
})
viewLifecycleOwner.lifecycleScope.launch {
viewModel.makeApiCall()
}
}
ViewModel:
class DeviceListViewModel : ViewModel() {
var recyclerListLiveData: MutableLiveData<ResponseData>
init {
recyclerListLiveData = MutableLiveData()
}
fun getRecycerListObserver(): MutableLiveData<ResponseData> {
return recyclerListLiveData
}
fun makeApiCall() {
viewModelScope.launch(Dispatchers.IO) {
try {
val retroInstance =
RetroInstance.getRetroInstance(MainActivity.BaseUrl).create(RetroService::class.java)
val response = retroInstance.getDataFromApi(1, Cmd.WIFIAPP_CMD_FILELIST)
recyclerListLiveData.postValue(response)
} catch (e: Exception) {
var response: ResponseData? = null
when (e) {
is ConnectException -> {
recyclerListLiveData.postValue(response)
}
is SocketTimeoutException -> {
recyclerListLiveData.postValue(response)
}
}
}
}
}
}
Service to make a command (processing results in the Handler):
class DeviceService {
private val handler: Handler
private var mJob: Job? = null
constructor(handler: Handler) {
this.handler = handler
}
fun sendCommand(cmd: Int) {
val service = RetroInstance.buildService(MainActivity.BaseUrl, RetroService::class.java)
mJob = CoroutineScope(Dispatchers.IO).launch {
val response = when (cmd) {
Cmd.WIFIAPP_CMD_MOVIE_GET_LIVEVIEW_FMT -> {
try {
service.getLinkFromApi(1, cmd)
} catch (e: Exception) {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Error in getting data").sendToTarget()
mJob?.cancel()
}
}
else -> {
try {
service.makeCommand(1, cmd)
} catch (e: Exception) {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Error in getting data").sendToTarget()
mJob?.cancel()
}
}
}
withContext(Dispatchers.Main) {
try {
when (cmd) {
Cmd.WIFIAPP_CMD_MOVIE_GET_LIVEVIEW_FMT -> {
handler.obtainMessage(Msg.MESSAGE_LINK_FORMAT, response).sendToTarget()
}
else -> {
handler.obtainMessage(Msg.MESSAGE_PAR_FUNCTION, response).sendToTarget()
}
}
} catch (e: Exception) {
when (e) {
is ConnectException -> {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Connection lost").sendToTarget()
}
is SocketTimeoutException -> {
handler.obtainMessage(Msg.MESSAGE_TOAST, "Connection lost").sendToTarget()
}
}
}
mJob?.cancelAndJoin()
}
}
}
}
Downloading a images:
class ThumbDownloader(dataAdapter: DeviceAdapter, data: ArrayList<MediaData>, file_path: String, swipe: SwipeRefreshLayout) : CoroutineScope {
private var job: Job = Job()
private var file_path: String
private var dataAdapter: DeviceAdapter
private var data: ArrayList<MediaData>
private var swipe: SwipeRefreshLayout
init {
this.data = data
this.file_path = file_path
this.dataAdapter = dataAdapter
this.swipe = swipe
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
fun cancel() {
job.cancel()
}
fun execute() = async {
var item: File? = null
for (i in data) {
val task = async(Dispatchers.IO) {
val url = i.fPath!!
val real_url = "${MainActivity.BaseUrl}$url"
item = NetworkUtil.downloadFile(real_url, file_path, false)
}
task.await()
if (item != null) {
dataAdapter.insertItem(i)
}
}
cancel()
swipe.isRefreshing = false
}
}
Any ideas how to come up with their synchronization while waiting for the previous commands to complete?

How to get variables out from a asynctask + try & catch block in kotlin (android studio)?

In these below two functions I am getting referrerUrl and addId. I want both of them to be fetched in onCreate but don't know how because it is in try & catch block also the getGaid() function is not running without AsyncTask.
fun getreferrUrl() {
//to install referrer client
val referrerClient = InstallReferrerClient.newBuilder(this).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
when (responseCode) {
InstallReferrerResponse.OK -> {
// Connection established.
try {
val response: ReferrerDetails = referrerClient.installReferrer
val referrerUrl = response.installReferrer
// here we need referrerUrl out from this fuction
} catch (e: RemoteException) {
e.printStackTrace()
}
}
//
fun getGaid() {
AsyncTask.execute {
try {
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(this)
val myId: String = if (adInfo != null) adInfo.id else null.toString()
//here we need myId out from this fuction
} catch (e: java.lang.Exception) {...}
}
}
In onCreate we need both of those strings.
// In onCreate
val url = "http://instbng.com?device_id=$device_id&
&kd_id=$kd_id&ref=$referrerUrl&gaid=$myId"
loadUrl(url)
Without coroutines, you can put the results in properties, and create a function that uses both properties and call it from both callbacks. I renamed your get... functions to fetch... since they are asynchronous. The word get in a function name implies they are synchronous.
private var referrerUrl: String? = null
private var myId: String? = null
override fun onCreate(bundle: SavedInstanceState?) {
super.onCreate(bundle)
//...
fetchReferrerUrl()
fetchGaId()
}
// proceeds with workflow if referrerUrl and myId are both available
private fun proceedIfReady() {
val referrer = referrerUrl ?: return
val id = myId ?: return
val url = "http://instbng.com?device_id=$device_id&kd_id=$kd_id&ref=$referrer&gaid=$idd"
loadUrl(url)
}
fun fetchReferrerUrl() {
val referrerClient = InstallReferrerClient.newBuilder(this).build()
referrerClient.startConnection(object : InstallReferrerStateListener {
override fun onInstallReferrerSetupFinished(responseCode: Int) {
when (responseCode) {
InstallReferrerResponse.OK -> {
// Connection established.
try {
val response: ReferrerDetails = referrerClient.installReferrer
referrerUrl = response.installReferrer
proceedIfReady()
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
}
//... handle closed connection callback
}
}
private fun fetchGaId() {
AsyncTask.execute {
try {
val adInfo = AdvertisingIdClient.getAdvertisingIdInfo(this)
runOnUiThread { // in a Fragment use view?.post
myId = if (adInfo != null) adInfo.id else null.toString()
proceedIfReady()
}
} catch (e: java.lang.Exception) {...}
}
}

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

Categories

Resources