So I wrote a Flutter App about 4 Months ago. Now I wanted to do a small change, but I can't compile the app anymore, because GeneratedPluginRegistrant.registerWith(this) doesn't work anymore, I didn't change the Kotlin code only the Dart code.
The "this" in "GeneratedPluginRegistrant.registerWith(this)" shows me this error:
Type mismatch.
Required: FlutterEngine!
Found: MainActivity
The MainActivity Class:
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.view.FlutterMain
class MainActivity : FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this) // here is the error: Type mismatch. Required: FlutterEngine! Found: MainActivity
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "helloFromNativeCode") {
val greetings = helloFromNativeCode()
result.success(greetings)
}
}
}
private fun helloFromNativeCode(): String {
return "Hello from Native Android Code"
}
companion object {
private const val CHANNEL = "flutter.native/helper"
}
}
And if is use:
import io.flutter.embedding.android.FlutterActivity
instead of
import io.flutter.app.FlutterActivity
I can use
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
but have trouble with:
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "helloFromNativeCode") {
val greetings = helloFromNativeCode()
result.success(greetings)
}
}
because i get an error on flutterView:
Unresolved reference: flutterView
The code would look like this:
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
import io.flutter.view.FlutterMain
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result -> // here is the error
if (call.method == "helloFromNativeCode") {
val greetings = helloFromNativeCode()
result.success(greetings)
}
}
}
private fun helloFromNativeCode(): String {
return "Hello from Native Android Code"
}
companion object {
private const val CHANNEL = "flutter.native/helper"
}
}
I hope someone can help me.
Instead of flutterView use flutterEngine.getDartExecutor().
I spent days trying to figure out how to add a Flutter UI to my existing Android App. The biggest challenge was getting the MethodChannel to work with FlutterActivity being called from MainActivity. I know this is a little different than the question asked here, but this post was returned when I did searches for 'Android FlutterActivity MethodChannel'. After going through many resources on how to do this, I finally found my solution here:
https://github.com/flutter/samples/tree/master/add_to_app/android_using_plugin/app/src/main/java/dev/flutter/example/androidusingplugin
Initially, in Android Studio, with the existing app opened, I tapped File, New, New Module, Flutter Module. I received an error and had to perform manual steps.
My objective is to launch FlutterActivity (opens main.dart in the flutter_module) in MainActivity - onCreate, then develop Flutter 'screens' leveraging as much native Flutter code as possible, with limited Platform calls using the MethodChannel. As I develop replacement Flutter code, I will continue to comment on the existing Android Code.
Here is what finally worked for me:
../App_Project/Android/Existing_Android_App/settings.gradle
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(settingsDir.parentFile, '../flutter_module/.android/include_flutter.groovy'))
include ':flutter_module’
project(':flutter_module’).projectDir = new File('../../flutter_module’)
rootProject.name=‘existing_android_app’
../App_Project/Android/Existing_Android_App/app/build.gradle
dependencies {
…
implementation project(':flutter')
}
../App_Project/Android/Existing_Android_App/app/src/main/AndroidManifest.xml
<activity
android:name="io.flutter.embedding.android.FlutterActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize" />
../App_Project/Android/Existing_Android_App/app/src/main/java/com/existing_android_app/MainActivity.java
package com.existing_android_app;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.embedding.engine.FlutterEngineCache;
import io.flutter.embedding.engine.dart.DartExecutor;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends AppCompatActivity {
final String ENGINE_ID = "1";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FlutterEngine flutterEngine = new FlutterEngine(this);
flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
FlutterEngineCache.getInstance().put(ENGINE_ID, flutterEngine);
MethodChannel channel = new MethodChannel(flutterEngine.getDartExecutor(), "com.existing_android_app/myMethodChannel");
channel.setMethodCallHandler(
new MethodChannel.MethodCallHandler() {
#Override
public void onMethodCall(#NonNull MethodCall call, #NonNull MethodChannel.Result result) {
String url = call.argument("url");
if (call.method.equals("openBrowser")) {
openBrowser(url);
}
else {
result.notImplemented();
}
}
});
startActivity(FlutterActivity.withCachedEngine(ENGINE_ID).build(this));
}
void openBrowser(String url) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
this.startActivity(intent);
}
}
../App_Project/flutter_module/lib/home_page.dart
class AppHomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<AppHomePage> {
static const platform = const MethodChannel(‘com.existing_android_app/myMethodChannel’);
Future<void> _openBrowser() async {
try {
final int result = await platform.invokeMethod('openBrowser', <String, String> { 'url': "http://bing.com" });
}
catch (e) {
print('***** _openBrowser error: ' + e.toString());
}
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: CustomAppBar(),
body: Column(
children: <Widget>[
RaisedButton(
label: Text('Search',
style: TextStyle(fontSize: 18.0),
),
onPressed: () { _openBrowser(); },
) // RaisedButton.icon
], // Widget
) // Column
) // Scaffold
); // SafeArea
}
You should use
import io.flutter.embedding.android.FlutterActivity;
and declare your patformChannel in
#Override
public void configureFlutterEngine(#NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
// Note: this method is invoked on the main thread.
// TODO
}
);
}
for more you can check the documentation: https://flutter.dev/docs/development/platform-integration/platform-channels
You can use method channel and flutter engine like this.
private val CHANNEL = "adb"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler { call, result ->
//You can use your custom function for example:
if (call.method.equals("checkingadb")) {
checkingadb(call, result)
} else {
result.notImplemented()
}
}
}
private fun checkingadb(call: MethodCall, result: MethodChannel.Result) {
if (Settings.Secure.getInt(this.getContentResolver(), Settings.Secure.ADB_ENABLED, 0) === 1) {
// debugging enabled
result.success(1)
} else {
// debugging is not enabled
result.success(0)
}
}
To pass flutter Engine value we can use provideFlutterEngine(this) method. And for flutterView we can use flutterEngine.dartExecutor. Here is the url for code snippet ,have answered it earlier
https://stackoverflow.com/a/67698834/11887774 .
Related
when i build smaple project , app has been crashed
when i see logcat , i see this:
90-30676/io.agora.agora_android_uikit E/agora.io: jni_generator_helper.h: (line 131): android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
what do i do ?
My code:
package io.agora.agora_android_uikit
import android.Manifest
import android.graphics.Color
import android.os.Bundle
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import androidx.appcompat.app.AppCompatActivity
import io.agora.agorauikit_android.AgoraButton
import io.agora.agorauikit_android.AgoraConnectionData
import io.agora.agorauikit_android.AgoraSettings
import io.agora.agorauikit_android.AgoraVideoViewer
import io.agora.agorauikit_android.requestPermission
import io.agora.rtc2.Constants
private const val PERMISSION_REQ_ID = 22
private val REQUESTED_PERMISSIONS = arrayOf<String>(
Manifest.permission.RECORD_AUDIO,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
#ExperimentalUnsignedTypes
class MainActivity : AppCompatActivity() {
var agView: AgoraVideoViewer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
try {
agView = AgoraVideoViewer(
this, AgoraConnectionData("*******"),
)
} catch (e: Exception) {
println("Could not initialise AgoraVideoViewer. Check your App ID is valid. ${e.message}")
return
}
val set = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)
this.addContentView(agView, set)
if (AgoraVideoViewer.requestPermission(this)) {
agView!!.join("test", role = Constants.CLIENT_ROLE_BROADCASTER)
} else {
val joinButton = Button(this)
// this#MainActivity.runOnUiThread(java.lang.Runnable {
runOnUiThread {
Runnable { joinButton.text = "Allow Camera and Microphone, then click here" }
}
// })
joinButton.setOnClickListener {
if (AgoraVideoViewer.requestPermission(this)) {
// (joinButton.parent as ViewGroup).removeView(joinButton)
agView!!.join("test", role = Constants.CLIENT_ROLE_BROADCASTER)
}
}
// joinButton.setBackgroundColor(Color.GREEN)
// joinButton.setTextColor(Color.RED)
this.addContentView(
joinButton,
FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, 300)
)
}
}
i want to solve my issue
Please wrap with function and make annotations there instead of mentioning in the main content view and wrap using UI thread.And refer sample github agora sdk github for reference - https://github.com/AgoraIO-Community/VideoUIKit-Android
I'm working with AWS Amplify + Android Studio. In the backend.kt file, I call UserData.setSignedIn which gives me a Unresolved reference: setSignedIn error. I imported the file where this function is but it still gives me that error. Here are the two files:
package com.wcsng.dlocapp
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.service.autofill.UserData
import android.util.Log
import com.amplifyframework.AmplifyException
import com.amplifyframework.api.aws.AWSApiPlugin
import com.amplifyframework.auth.AuthChannelEventName
import com.amplifyframework.auth.AuthException
import com.amplifyframework.auth.cognito.AWSCognitoAuthPlugin
import com.amplifyframework.auth.cognito.AWSCognitoAuthSession
import com.amplifyframework.auth.result.AuthSessionResult
import com.amplifyframework.auth.result.AuthSignInResult
import com.amplifyframework.core.Amplify
import com.amplifyframework.core.InitializationStatus
import com.amplifyframework.hub.HubChannel
import com.amplifyframework.hub.HubEvent
import com.wcsng.dlocapp.UserData.setSignedIn
object Backend {
private const val TAG = "Backend"
fun initialize(applicationContext: Context) : Backend {
try {
Amplify.addPlugin(AWSCognitoAuthPlugin())
Amplify.addPlugin(AWSApiPlugin())
Amplify.configure(applicationContext)
Log.i(TAG, "Initialized Amplify")
} catch (e: AmplifyException) {
Log.e(TAG, "Could not initialize Amplify", e)
}
Log.i(TAG, "registering hub event")
// listen to auth event
Amplify.Hub.subscribe(HubChannel.AUTH) { hubEvent: HubEvent<*> ->
when (hubEvent.name) {
InitializationStatus.SUCCEEDED.toString() -> {
Log.i(TAG, "Amplify successfully initialized")
}
InitializationStatus.FAILED.toString() -> {
Log.i(TAG, "Amplify initialization failed")
}
else -> {
when (AuthChannelEventName.valueOf(hubEvent.name)) {
AuthChannelEventName.SIGNED_IN -> {
updateUserData(true)
Log.i(TAG, "HUB : SIGNED_IN")
}
AuthChannelEventName.SIGNED_OUT -> {
updateUserData(false)
Log.i(TAG, "HUB : SIGNED_OUT")
}
else -> Log.i(TAG, """HUB EVENT:${hubEvent.name}""")
}
}
}
}
Log.i(TAG, "retrieving session status")
// is user already authenticated (from a previous execution) ?
Amplify.Auth.fetchAuthSession(
{ result ->
Log.i(TAG, result.toString())
val cognitoAuthSession = result as AWSCognitoAuthSession
// update UI
this.updateUserData(cognitoAuthSession.isSignedIn)
when (cognitoAuthSession.identityId.type) {
AuthSessionResult.Type.SUCCESS -> Log.i(TAG, "IdentityId: " + cognitoAuthSession.identityId.value)
AuthSessionResult.Type.FAILURE -> Log.i(TAG, "IdentityId not present because: " + cognitoAuthSession.identityId.error.toString())
}
},
{ error -> Log.i(TAG, error.toString()) }
)
return this
}
private fun updateUserData(withSignedInStatus : Boolean) {
UserData.setSignedIn(withSignedInStatus) // ERROR IS HERE
}
fun signOut() {
Log.i(TAG, "Initiate Signout Sequence")
Amplify.Auth.signOut(
{ Log.i(TAG, "Signed out!") },
{ error -> Log.e(TAG, error.toString()) }
)
}
fun signIn(callingActivity: Activity) {
Log.i(TAG, "Initiate Signin Sequence")
Amplify.Auth.signInWithWebUI(
callingActivity,
{ result: AuthSignInResult -> Log.i(TAG, result.toString()) },
{ error: AuthException -> Log.e(TAG, error.toString()) }
)
}
// Backend.kt
// pass the data from web redirect to Amplify libs
fun handleWebUISignInResponse(requestCode: Int, resultCode: Int, data: Intent?) {
Log.d(TAG, "received requestCode : $requestCode and resultCode : $resultCode")
if (requestCode == AWSCognitoAuthPlugin.WEB_UI_SIGN_IN_ACTIVITY_CODE) {
Amplify.Auth.handleWebUISignInResponse(data)
}
}
}
package com.wcsng.dlocapp
import android.graphics.Bitmap
import android.location.Location
import android.provider.ContactsContract
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.amplifyframework.datastore.generated.model.LocationData
// a singleton to hold user data (this is a ViewModel pattern, without inheriting from ViewModel)
object UserData {
private const val TAG = "UserData"
//
// observable properties
//
// signed in status
private val _isSignedIn = MutableLiveData<Boolean>(false)
var isSignedIn: LiveData<Boolean> = _isSignedIn
fun setSignedIn(newValue : Boolean) {
// use postvalue() to make the assignation on the main (UI) thread
_isSignedIn.postValue(newValue)
}
// a note
data class Location(val id: String, val name: String, val map: String, var location: String) {
override fun toString(): String = name
// return an API NoteData from this Note object
val data : LocationData?
get() = LocationData.builder()
.name(this.name)
.id(this.id)
.map(this.map)
.location(this.location)
.build()
// static function to create a Note from a NoteData API object
companion object {
fun from(locationData : LocationData) : Location {
val result = Location(locationData.id, locationData.name, locationData.map, locationData.location)
return result
}
}
}
}
Thanks for the help
I'm trying to implement an app using Flutter. The app should be able to send messages from the smartphones to a server using FCM or receive messages from the server via FCM.
I implemented the FCM functionality using the firebase_messaging plugin (https://pub.dev/packages/firebase_messaging). Everything is working fine for downstream messages (server -> device). Now I tried to add upstream messages (device -> server). As far as I know from the docs, the plugin does not support upstream messages yet. So I began to write native code to send the messages to the server and call this code via platform channel functionality (https://flutter.dev/docs/development/platform-integration/platform-channels?tab=android-channel-kotlin-tab).
Initial MainActivity.kt:
import android.os.Bundle
import io.flutter.app.FlutterActivity
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
GeneratedPluginRegistrant.registerWith(this)
}
}
Added the method channel example from Flutter docs. Using the imports specified in the Flutter docs:
private val CHANNEL = "samples.flutter.dev/battery"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// Note: this method is invoked on the main thread.
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
But unfortunately there occurs a problem. The firebase_messaging plugin requires the mainactivity.kt to extend io.flutter.app.FlutterActivity. Platform channels require the mainactivity.kt to extend io.flutter.embedding.android.FlutterActivity.
Is there any way to use both - firebase_messaging plugin and method channels?
I compared my code to the example project from the firebase_messaging plugin: https://github.com/FirebaseExtended/flutterfire/blob/master/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/MainActivity.java
Then I realized that the example uses the io.flutter.embedding.android.FlutterActivity too. After changing the import statements to the io.flutter.embedding... package I removed the GeneratedPluginRegistrant.registerWith(this) as GeneratedPluginRegistrant is an import from io.flutter.plugins.GeneratedPluginRegistrant and does not seem to match the embedding package. Then I checked the fields and functions in io.flutter.embedding.engine.FlutterEngine again and added following line: flutterEngine?.plugins?.add(FirebaseMessagingPlugin())
After this I recompiled the app and at the start it prints out the FCM device ID and the battery status successfully.
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Bundle
import androidx.annotation.NonNull
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin
class MainActivity : io.flutter.embedding.android.FlutterActivity() {
private val CHANNEL = "samples.flutter.dev/battery"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
flutterEngine?.plugins?.add(FirebaseMessagingPlugin())
}
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
// Note: this method is invoked on the main thread.
call, result ->
if (call.method == "getBatteryLevel") {
val batteryLevel = getBatteryLevel()
if (batteryLevel != -1) {
result.success(batteryLevel)
} else {
result.error("UNAVAILABLE", "Battery level not available.", null)
}
} else {
result.notImplemented()
}
}
}
private fun getBatteryLevel(): Int {
val batteryLevel: Int
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
} else {
val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
}
return batteryLevel
}
}
I want to use an Event Channel to get a data stream from the Spotify SDK. On the native side, I can automatically display the status of a current song by subscribing to my PlayerState. My goal is to be able to access this data stream with my Flutter app. However, I don't get any real data, just a MapStream:
Instance of '_MapStream<dynamic, double>'
According to my StreamBuilder, this MapStream has no data. Where is the mistake? How do I push the data from the Native to Flutter side?
Native Code:
package test.test.spotifysdk04
import com.spotify.android.appremote.api.ConnectionParams
import com.spotify.android.appremote.api.Connector
import com.spotify.android.appremote.api.SpotifyAppRemote
import com.spotify.protocol.types.PlayerState
import com.spotify.sdk.android.authentication.AuthenticationClient
import com.spotify.sdk.android.authentication.AuthenticationRequest
import com.spotify.sdk.android.authentication.AuthenticationResponse
import io.flutter.Log
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry.Registrar
import com.spotify.protocol.types.Track
class Spotifysdk04Plugin(private var registrar: Registrar): MethodCallHandler, EventChannel.StreamHandler {
private val clientId = "65fd9b2b0ee74575a6d26223a1675917"
private val redirectUri = "spotify-flutter://callback"
private var spotifyAppRemote: SpotifyAppRemote? = null
private val REQUEST_CODE = 1337
private var mEventSink: EventChannel.EventSink? = null
companion object {
#JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "spotifysdk")
channel.setMethodCallHandler(Spotifysdk04Plugin(registrar))
val eventChannel = EventChannel(registrar.messenger(), "timerStream")
eventChannel.setStreamHandler(Spotifysdk04Plugin(registrar))
}
}
override fun onMethodCall(call: MethodCall, result: Result) {
if (call.method == "loginAppRemote") {
val connectionParams = ConnectionParams.Builder(clientId)
.setRedirectUri(redirectUri)
.showAuthView(true)
.build()
SpotifyAppRemote.connect(registrar.context(), connectionParams, object : Connector.ConnectionListener {
override fun onConnected(appRemote: SpotifyAppRemote) {
spotifyAppRemote = appRemote
Log.d("Spotify App Remote Login", "Connected!")
result.success(true)
}
override fun onFailure(throwable: Throwable) {
Log.e("Spotify App Remote Login", "Error!", throwable)
result.success(false)
}
})
} else if(call.method == "loginSpotifyAuthentication") {
try {
AuthenticationClient.openLoginActivity(
registrar.activity(), REQUEST_CODE,
AuthenticationRequest.Builder(clientId,AuthenticationResponse.Type.TOKEN,redirectUri)
.setScopes(arrayOf("user-modify-playback-state")).build())
}catch (err:Throwable){
Log.v("getAuthTokenError",err.message.toString())
}
registrar.addActivityResultListener { requestCode, resultCode, intent ->
if (requestCode == REQUEST_CODE){
val response = AuthenticationClient.getResponse(resultCode, intent).accessToken
result.success(response)
}
true
}
} else {
result.notImplemented()
}
}
override fun onCancel(arguments: Any?) {
mEventSink = null
}
override fun onListen(arguments: Any?, eventSink: EventChannel.EventSink?) {
createListener(eventSink)
}
fun createListener(event: EventChannel.EventSink?) {
if (spotifyAppRemote != null) {
spotifyAppRemote!!.playerApi.subscribeToPlayerState()
.setEventCallback { playerState: PlayerState? ->
var position = playerState!!.playbackPosition.toDouble()
Log.d("playbackPosition", position.toString())
event?.success(position)
}
}
}
}
Dart Plugin Code:
import 'dart:async';
import 'package:flutter/services.dart';
class Spotifysdk04 {
static const MethodChannel _channel = const MethodChannel('spotifysdk');
static const EventChannel _timerEventChannel = const EventChannel('timerStream');
static Stream<double> _timerStream;
static Future<bool> get loginAppRemote async {
final bool connected = await _channel.invokeMethod('loginAppRemote');
return connected;
}
static Future<String> get loginSpotifyAuthentication async {
final String accessToken =
await _channel.invokeMethod('loginSpotifyAuthentication');
return accessToken;
}
static Stream<double> get timerStream {
_timerStream =
_timerEventChannel.receiveBroadcastStream().map<double>((value) => value);
print(_timerStream);
return _timerStream;
}
}
Flutter Code:
//...
StreamBuilder(
stream: Spotifysdk04.timerStream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
print(snapshot);
if (snapshot.hasData) {
return Text(
'Pressure Stream ${snapshot.data}',
style: TextStyle(
color: Colors.white,
),
);
}
return Text(
'No data',
style: TextStyle(
color: Colors.white,
),
);
}),
//...
You're almost there.
_timerStream = _timerEventChannel.receiveBroadcastStream();
That's already enough. Usually you would listen to a stream using its StreamSubscription<T> listen(void onData(T)) method:
_timerStream = _timerEventChannel.receiveBroadcastStream();
var subscription = _timerStream.listen(onData);
void onData(dynamic d) {
print("Data: $d");
}
A StreamBuilder however already takes care of all the listening logic for you.
I am able to tun my test but it fails. The problem is, mocked method is still returning wrong data. This is my method that I want to test it:
fun getTextByLanguage(list: List<TitleModel>) : String {
val deviceLanguage = Locale.getDefault().language
var default = ""
for (item in list) {
if (item.culture.contains(deviceLanguage, true)) return item.value
if (item.culture.contains("en", true)) default = item.value
}
return default
}
And this is how I am testing the method:
import junit.framework.TestCase.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.api.mockito.PowerMockito.*
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import java.util.Locale
#RunWith(PowerMockRunner::class)
#PrepareForTest(Locale::class)
class AppConfigUtilityByPowerMockTest {
#Test
fun `getTextByLanguage, test en`() {
mockStatic(Locale::class.java)
val mockedLocal = mock(Locale::class.java)
`when`(Locale.getDefault()).thenReturn(mockedLocal)
`when`(mockedLocal.language).thenReturn("en")
val list = listOf(TitleModel("en-ca", "Home"), TitleModel("fr-ca", "HomeFr"))
val actual = getTextByLanguage(list)
assertEquals("Home", actual)
}
#Test
fun `getTextByLanguage, test fr`() {
mockStatic(Locale::class.java)
val mockedLocal = mock(Locale::class.java)
`when`(Locale.getDefault()).thenReturn(mockedLocal)
`when`(mockedLocal.language).thenReturn("fr")
val list = listOf(TitleModel("en-ca", "Home"), TitleModel("fr-ca", "HomeFr"))
val actual = getTextByLanguage(list)
assertEquals("HomeFr", actual)
}
}
The first test cases without problem but the second one fails. This is the out put:
junit.framework.ComparisonFailure: expected:<Home[Fr]> but was:<Home[]>
Expected :HomeFr
Actual :Home
One way to answer the question is to get rid of Local class from the method and do stuff in Kotlin way. So, what I did is to change my original method like this:
fun getTextByLanguage(list: List<TitleModel>, deviceLanguage: String = getDeviceLanguage()) : String {
var default = ""
for (item in list) {
if (item.culture.contains(deviceLanguage, true)) return item.value
if (item.culture.contains("en", true)) default = item.value
}
return default
}
fun getDeviceLanguage(): String {
return Locale.getDefault().language
}
Now my test class looks like this:
import junit.framework.TestCase.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import java.util.Locale
#RunWith(PowerMockRunner::class)
#PrepareForTest(Locale::class)
class AppConfigUtilityByPowerMockTest {
#Test
fun `getTextByLanguage, test en`() {
val list = listOf(TitleModel("en-ca", "Home"), TitleModel("fr-ca", "HomeFr"))
val actual = getTextByLanguage(list, "en")
assertEquals("Home", actual)
}
#Test
fun `getTextByLanguage, test fr`() {
val list = listOf(TitleModel("en-ca", "Home"), TitleModel("fr-ca", "HomeFr"))
val actual = getTextByLanguage(list, "fr")
assertEquals("HomeFr", actual)
}
}
Although my test passes, I am still willing to see your recommendation and down to accept your answer to my question.