I am trying to create a Android app which launches flutter inside it. I have learnt that we can pass data to flutter via MethodChannels like this:
MethodChannel(
FlutterEngineCache.getInstance().get(NEARBY_PLACES_ENGINE_KEY)?.dartExecutor?.binaryMessenger,
"APP_CHANNEL"
).setMethodCallHandler { call, result ->
if (call.method == "getGreetings") {
val coordinates = "Hello"
result.success(coordinates)
}
}
startActivity(FlutterActivity
.withCachedEngine(NEARBY_PLACES_ENGINE_KEY)
.build(this))
This is possible when I have access to FlutterEngine instance (In this case with help of FlutterEngineCache). But how do we get the BinaryMessenger of a engine created with FlutterActivity.withNewEngine() ? Please help. TIA!
Okay so I found the way to do it.
Created a custom FlutterActivity class and added that in the manifest instead of the one from io.flutter.embedding.android.FlutterActivity. The custom FlutterActivity extends from FlutterActivity, and overrides some methods to be able to access the newEngine.
class CustomFlutterActivity : FlutterActivity() {
companion object {
var methodChannelInvoker: (MethodCall, Result) -> Unit = { _, _ -> }
fun withCachedEngine(cachedEngineId: String): CachedEngineIntentBuilder {
return CachedEngineIntentBuilder(CompassFlutterActivity::class.java, cachedEngineId)
}
fun withNewEngine(): NewEngineIntentBuilder {
return NewEngineIntentBuilder(CompassFlutterActivity::class.java)
}
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "APP_CHANNEL")
.setMethodCallHandler { call, result ->
methodChannelInvoker(call, result)
}
}
}
and at the launch site,
CustomFlutterActivity.methodChannelInvoker = { call, result ->
if (call.method == "getGreetings") {
val greetings = "Hello there!"
result.success(coordinates)
}
}
startActivity(CustomFlutterActivity
.withNewEngine()
.initialRoute("/custom_route")
.build(this))
This way, when the new engine launches, you get callback at configureFlutterEngine with new engine as parameter, we can simply access dartExecutor at that point.
Oh yes, and don't forget to add the CustomFlutterActivity in the manifest instead of FlutterActivity.
Related
I am trying to open FlutterActivity in my existing android application. Before I was creating new flutter engine every time I was opening activity like this:
FlutterActivity
.withNewEngine()
.build(context)
And everything was working fine besides a little lag while opening the activity. To get rid of the lag I wanted to switch to using cached engine. I followed this official tutorial: LINK
And ended up with smething like this:
In my Application class:
class App : Application() {
lateinit var flutterEngine: FlutterEngine
override fun onCreate() {
...
flutterEngine = FlutterEngine(this)
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
FlutterEngineCache
.getInstance()
.put("myEngineId", flutterEngine)
}
}
And later in my application on the button click, in the same place that I was successfully opening FlutterActivity:
FlutterActivity
.withCachedEngine("myEngineId")
.build(context)
So I basically followed the all the instructions but the effect that I get now is after the button click there is even longer lag than before and then there is only black screen being displayed. My flutter screen is not displayed and application is kind of frozen I can't go back or do anything. There is also no error or any useful info in the logs. I have no idea what is going on. What am I doing wrong?
To Use cached FlutterEngine
In FlutterActivity you must declare provideFlutterEngine method.
class DemoActivity : FlutterActivity() {
override fun provideFlutterEngine(context: Context): FlutterEngine? =
FlutterEngineCache.getInstance().get(FlutterConstants.ENGINE_ID)
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "demo-channel")
.setMethodCallHandler { call, result ->
if (call.method == "demo-method") {
demoMethod()
result.success(null)
} else {
result.notImplemented()
}
}
}
private fun demoMethod() {
// Do native code
}
}
I initialized realm in my Application classe like this:
val realmConfig = RealmConfiguration.Builder(schema =
setOf(User::class)
) .name("myrealm_DB.db")
.schemaVersion(1)
.deleteRealmIfMigrationNeeded()
.log(LogLevel.ALL)
.build()
realm = Realm.open(configuration = realmConfig)
What would be the proper way to get an instance of realm in another activity? Since in the kotlin sdk we dont have Realm.getDefaultInstance() method? Is there a way to create like a global RealmManager class?
Since it's very specific to each pattern, and model that you used while developing application and enterprise models there is not a basic answer, unfortunately. Still, I'll share one pattern I used to do while working with Realm.
object RealmProcessor {
private var realmInstance: Realm? = nil
fun startRealm(someSpecialProperties: <Type>, completion: () -> Unit? = {}){
runInSafeQueue ({
try {
val config = RealmConfiguration.Builder(
setOf(
User::class,
Message::class,
MessageEmbed::class,
MessageViewer::class,
MessageRecipient::class,
Inbox::class,
InboxUser::class,
Sync::class
)
)
config.schemaVersion(1)
config.deleteRealmIfMigrationNeeded()
// We're using also Realm-JS, since we want the same directory that the JS thread created.
config.name("my-percious-realm-$my_custom_property.realm")
realmInstance = Realm.open(config.build())
} catch(e: Error) {
logError("Realm start error", thrown = e)
}
})
}
// Since the threads has to be same for write operations which we used for opening Realm making it singleton with one dispatcher.
private fun runInSafeQueue(runner: suspend () -> Unit?, didCatch: (Error) -> Unit = { _ -> }) {
GlobalScope.launch {
try {
runner()
} catch (e: Error) {
didCatch(e)
}
}
}
// This is very basic example with making this Object class a generic Realm accessor so you initialize it in very first activity that your app used you can easily keep accessing it from any activity
inline fun <reified T: BaseRealmObject>getFromRealm(id: Int): RealmResults<T>? {
return realmInstance?.query(T::class, "id == $0", id)?.find()
}
fun <T: RealmObject>createInRealm(objectToCopyRealm: T) {
runInSafeQueue({
realmInstance?.write {
copyToRealm(objectToCopyRealm)
null
}
})
}
fun changeUserValue(changedValue: Int) {
runInSafeQueue({
realmInstance?.write {
val objectToChange = getFromRealm<User>(20)
objectToChange?.first()?.personalMessageRoom = changedValue
}
})
}
}
Hope helps anyone they looking for some point for the start
I'm working on a library that has a couple of ready-made activities.
So far i have my activities in the library, and in the main app, i call it normally with registerForActivityResult to start it.
this means whoever is using my library would be able to see the whole activity.
what i would like to do, is to have the developer call a method in the library class and ask it to do an action, and in the library that method would on its own start the activity, register it for result, and return the result to the calling class through an interface.
the below is what i tried but it gives me error LifecycleOwner is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED
private fun launchScannerActivity(activity: FragmentActivity, callback: ScannerCallback) {
val scanResult =
activity.registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
callback.onResult(it.data?.getStringExtra("Some Key") ?: "")
} else {
callback.onFail()
}
}
val intent = Intent(activity, ScannerActivity::class.java)
scanResult.launch(intent);
}
why do i need this:
This library would be an SDK for a SAAS product, so we would like to abstract and obfuscate as much of the implementation as possible from our clients.
You can't really communicate between Activities using interfaces, at least not in a way that is somewhat concise and isn't very prone to leaking. What you can do is expose your own Activity result contract. Then your API could be as simple as some of the ones in ActivityResultContracts. You can look at the source code there to see how to implement it.
Maybe something like this:
class ScannerResultContract : ActivityResultContract<Unit, String?>() {
override fun createIntent(context: Context, input: Unit?): Intent {
return Intent(context, ScannerActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): String? {
return if (resultCode == Activity.RESULT_OK) {
intent?.getStringExtra("Some Key")
} else {
null
}
}
}
Client usage:
// In activity or fragment:
val getScannerResult = registerForActivityResult(ScannerResultContract()) { resultString ->
if (resultString != null) {
// use it
} else {
// log no result returned
}
}
//elsewhere:
someListener.setOnClickListener {
getScannerResult.launch()
}
I am using the Play Services Auth api Phone and so far I have the foll
fun startSmsListener() {
val client = SmsRetriever.getClient(applicationContext /* context */);
val task = client.startSmsRetriever();
task.addOnSuccessListener(object : OnSuccessListener<Void> {
override fun onSuccess(p0: Void?) {
//do somethin
}
})
task.addOnFailureListener(object : OnFailureListener {
override fun onFailure(p0: Exception) {
//Handle error
}
})
}
Now I want to put this in an SmsManager class and convert it into an Single/Observable so I can handle it in a reactive way in my viewmodel. How can I do that?
So far I've got this:
var single = Single.create(SingleOnSubscribe<Void> { e ->
val task = client.startSmsRetriever()
task.addOnSuccessListener {
e.onSuccess(it)
}
task.addOnFailureListener {
e.onError(it)
}
})
But I am unsure as to whether this code is correct or not, whether there is something im missing like removing the listeners after disposal.
Any help?
You are interested in a "boolean" value - either connected or not connected, thus instead of Single you should use Completable:
Completable.create { emitter ->
val client = SmsRetriever.getClient(applicationContext)
val task = client.startSmsRetriever()
task.addOnSuccessListener { emitter.onComplete() }
task.addOnFailureListener { emitter.tryOnError(it) }
}
While creating a Completable manually will work, you might also have a look at the RxTask project. It provides "RxJava 2 binding for Google Play Services Task APIs".
If you need it just in one place, an extra library would certainly be an overkill. But if you plan to use more Play Services together with RxJava, it might be worth a look...
It doesn't (yet) provide a wrapper explicitly for SmsRetriever, but the general task helper classes would probably be enough:
val client = SmsRetriever.getClient(applicationContext)
val smsReceiver = CompletableTask.create(client::startSmsRetriever)
I'm new to writing tests and using Mockito.
I've read the similar topics here on Stackoverflow and made the suggested changes, making sure that regarded classes / interfaces / methods are open.
I tried to follow this
Mocking the constructor injected dependencies
This is the test I came up with so far
class RegistrationPresenterTest {
#Test
fun testRegisterSuccess() {
val mockService = mock<IHerokuInteractor>()
val mockLocal = mock<ILocalStorageInteractor>()
val mockView = mock<RegisterView>()
val mockRegistrationResponse = HerokuRegisterResponse("hash")
val mockPair = ImeiPair("imei","hash")
val presenter = RegisterPresenterImpl(mockLocal,mockService)
whenever(mockService.register(any())).thenReturn(Observable.just(mockRegistrationResponse))
whenever(mockLocal.clearPreferences()).thenReturn(Observable.just(true))
whenever(mockLocal.putImeiPair(any())).thenReturn(Observable.just(true))
//whenever(presenter.writeImeiPairLocally(any())) How do I specify parameters since it uses a parameter from the register method?
presenter.bindView(mockView)
presenter.register("imei","male")
verify(mockService, times(1)).register(any())
verify(mockLocal,times(1)).clearPreferences()
verify(mockLocal,times(1)).putImeiPair(any())
verify(mockView,times(1)).moveToMain()
}
but the response I keep getting is
Wanted but not invoked:
registerPresenterImpl.writeImeiPairLocally(
<any com.company.appname.model.ImeiPair>
);
Actually, there were zero interactions with this mock.
I got this response even when I don't mention that method in the test.
This is my presenter register method. I've changed the classes / interfaces & methods involved to open (kotlin). I believe override methods are open by nature in kotlin.
open class RegisterPresenterImpl #Inject constructor(val localStorage : ILocalStorageInteractor, var herokuService : IHerokuInteractor)
override fun register(imei : String, gender : String){
subscription = herokuService.register(RegisterObject(imei,gender)).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
{
registrationResult ->
Log.d(TAG,"${registrationResult}")
if(registrationResult.imei_hash != null){
writeImeiPairLocally(ImeiPair(imei,registrationResult.imei_hash))
}
else{
Log.e(TAG,"User already exists")
}
},
{
errorResponse -> Log.e(TAG,"Could not register user ${errorResponse.message}")
}
)
addSubscription(subscription)
}
and similarly the
open fun writeImeiPairLocally(pair : ImeiPair){
subscription = localStorage.clearPreferences().flatMap {
cleared -> localStorage.putImeiPair(pair)}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
{
booleanResult -> view?.moveToMain()
},
{
errorResponse -> Log.e(TAG,"Could not write ImeiPair to SharedPreferences ${errorResponse.message}")
}
)
addSubscription(subscription)
}
Here is interfaces
open interface ILocalStorageInteractor : ILocalStorage{
fun getImeiPair() : Observable<ImeiPair>
fun putImeiPair(pair: ImeiPair) : Observable<Boolean>
}
open interface ILocalStorage {
fun clearPreferences() : Observable<Boolean>
}
All help is appreciated.
If you are using plain jUnit, then your AndroidSchedulers.mainThread() is null. That's why onNext is not called.
You need to override Schedulers in a setUp() method with:
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
#Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate(); // or .test()
}
});
To avoid concurrency in tests, I would recommend to override Schedulers.io() like this:
RxJavaHooks.setOnIOScheduler(scheduler1 -> Schedulers.immediate());
If you are going to use TestScheduler, don't forget to call TestScheduler.triggerActions() method.
Also don't forget to unregister Schedulers in tearDown() like this:
RxJavaHooks.reset();
RxAndroidPlugins.getInstance().reset();
AndroidSchedulers.reset();
Schedulers.reset();