I'm currently adding a view developed using Flutter to an existing Android app. I have been following the tutorials found in the Flutter website and decided to used a cached engine in order to minimize the delay that users may experience when navigating to the Flutter portion of the app. In order to do so, you must launch your Flutter activity like
startActivity(
FlutterActivity
.withCachedEngine("my_engine_id")
.build(this) // this is a Context
)
After a while I need to wirte a method channel to communicate from the Flutter portion of the app back to the Android host app, so I followed the instructions found in another of Flutter's tutorials, where it is shown that the activity that implements the channel must extend FlutterActivity.
So my problem is that I'm not sure how to initialize this activity using a cached engine, since I obviously can't use FlutterActivity.withCachedEngine anymore. Has anyone solved this already?
After looking at FlutterActivity documentation I found the provideFlutterEngine method. The doc description clearly states that:
This hook is where a cached FlutterEngine should be provided, if a cached FlutterEngine is desired.
So the final implementation of my class looks like this now
class MyActivity : 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, "my-channel")
.setMethodCallHandler { call, result ->
if (call.method == "my-method") {
myMethod()
result.success(null)
} else {
result.notImplemented()
}
}
}
private fun myMethod() {
// Do native stuff
}
}
And I simply start it writing startActivity(Intent(this, MyActivity::class.java))
Related
I am trying to emit an event using DeviceEventManagerModule.RCTDeviceEventEmitter in React Native, but because I wish to call it from a Service, I don't know how to get the reactContext. My code looks like this:
class TimerService : Service() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val reactContext = ???
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("tick", "tock");
}
}
If the essence of what I am trying to do is wrong I'm happy to change anything and everything.
For more context, what I'm trying to do is keep track of rest timers in-between sets at the gym. Full code can be found here. I wanted to add a page to display the currently running rest timer, where the state is being stored in TimerService.
I tried to cast applicationContext to ReactContext like:
(applicationContext as ReactApplicationContext)
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("tick", "tock");
But was given an error saying I can't cast ApplicationContext to ReactApplicationContext. I also tried to figure out how I could pass reactApplicationContext down to my Service, but got a bit stumped on trying to parcel it for an Intent.
I am using Espresso with Kotlin for the UI test automation. I am trying to find a proper way to restart the app during the test and start it again, so the test scenario is the following:
start the app, go to login page
force close the app and open it again (basically restart it)
check some stuff etc
The way our UI tests are organized:
there is a test class where I have rules
val intent = Intent(ApplicationProvider.getApplicationContext(), MainActivity::class.java)
.putExtra(UI_TEST_INTENT, true)
#get:Rule
val rule = ActivityScenarioRule<MainActivity>(intent)
there Before/After functions and tests functions in this class
What I want is to have generic restartApp function in separated class, let's say TestUtils and to be able to call it at any point of time, when is needed.
So far I didn't find a solution. There are some similar questions on stackoverflow, but I am not sure I understand how to work with the answers I found, like this:
with(activityRule) {
finishActivity()
launchActivity(null)
}
Since ActivityTestRule is deprecated and documentation asking to use ActivityScenarioRule, I tried this:
#get:Rule
val rule = ActivityScenarioRule<MainActivity>(intent)
private fun restart() {
rule.scenario.close()
rule.scenario.recreate()
}
but it gets java.lang.NullPointerException
another option is
private fun restart() {
pressBackUnconditionally()
Intents.release()
ActivityScenario.launch<MainActivity>(intent)
}
it works, app restarts but I can not interact with the app anymore, because for some reason there are two intents running now
Would be great to get an answer I can work with (I am quite new to Espresso)
Cheers
The solution is found:
private fun restart() {
Intents.release()
rule.scenario.close()
Intents.init()
ActivityScenario.launch<MainActivity>(intent)
}
Seems like the author's answer has some excess code. The following is enough
activityScenarioRule.scenario.close()
ActivityScenario.launch(YourActivity::class.java, null)
I would like my app tз display a notification of the incoming messages, but only when the application is active, similar to how many social apps do that.
I.e., the user has my messaging app open and he gets a notification slide in from top, within this Android application.
To my understanding, this is something that is called “in app messages” in Firebase.
However, I wouldn’t like to have firebase as a dependency, as I am not using any part of it: the notifications will be triggered by an open network connection that my app made.
I also so wouldn’t want to involve push notifications as I need this functionality only when the app is active.
What would be the best way to achieve this goal?
Basically what I am asking is how to make my own notification “bubble” in UI that shows up inside my app, similar to how it is done in messaging/dating apps (see Badoo, for example). Mainly I am wondering if there are any implemendations available that I could use or do I have to draw this stuff myself (using Fragments?)
It's a very broad question. So in broad strokes: Use some real time communications technology, such as sockets/websockets to listen for incoming messages, and hook up into lifecycle to start listening when the app moves into foreground (and stop when it moves out) [assuming that is the meaning of app being active - otherwise if you include foreground state, just start listening and don't unlisten) -
class MyListener : LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onMoveToForeground() {
listenForNotification()
//start listening
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onMoveToBackground() {
//stop listening
}
where listener would be something like this:
private suspend fun listenForNotification(){
withContext(Dispatchers.IO){
myApi.receive() {
println("this is my notification object: $it")
NotificationHelper.sendNotification($it.message)
}
}
}
And NotificationHelper would be based on Notification Manager to push local notifications (as you wanted them to slide from the top - look like any push notification). Pay close attention to the flags you use to send the notification to make sure it is received and processed by the currently opened activity (do more research on it, separate topic) https://developer.android.com/reference/android/app/NotificationManager
In that same activity use OnNewIntent to receive user's action of tapping on the notification and then do whatever you want to do with it.
Alternatively, do not use local notification but just develop your own UI where you would display these things messaging style. (edit: for example, like this - link. Another one for actually showing notifications without using Notifications lib -link
Or a combination of both local notifications and the above example.
Edit:
*You can also use Firebase messaging to display messages locally.* Yes you would still need a firebase json to init the app, but after that you can construct your messages locally and display them, so it a very lightweight dependency on two libs and aside from initializing you won't need anything else from the firebase server.
Below is an example with two types of messages, card and banner. And of course you can just take the full code on GitHub and extract the part you need and modify it as needed. (the method used here is public for testing the appearance of the message locally - I don't see anything wrong with using it as a vehicle to deliver local notifications, but again the option to take the code and modify is always there)
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.google.firebase.FirebaseApp
import com.google.firebase.inappmessaging.MessagesProto
import com.google.firebase.inappmessaging.display.FirebaseInAppMessagingDisplay
import com.google.firebase.inappmessaging.model.*
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
companion object {
val whiteHex = "#ffffff"
val magHex = "#9C27B0"
val appUrl ="app://open.my.app"
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val action: String? = intent?.action
val data: Uri? = intent?.data
data?.let {
helloTextView.text ="You just clicked from Firebase Message"
return
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
FirebaseApp.initializeApp(this)
val text = Text.builder()
.setHexColor(whiteHex)
.setText("Local Firebase Message Body")
.build()
val title = Text.builder()
.setHexColor(whiteHex)
.setText("Local Firebase Message Title")
.build()
val imageData = ImageData.builder()
.setImageUrl("https://homepages.cae.wisc.edu/~ece533/images/frymire.png")
.build()
val button = Button.builder()
.setButtonHexColor(whiteHex).setText(text).build()
val campaignMeta = CampaignMetadata("S", "D", true)
val primaryAction = Action.builder()
.setActionUrl(appUrl)
.setButton(button)
.build()
val fmessage = CardMessage.builder()
.setPrimaryAction(primaryAction)
.setBackgroundHexColor(magHex)
.setPortraitImageData(imageData)
.setTitle(title).build(campaignMeta)
val bannerMessage = BannerMessage.builder()
.setAction(primaryAction)
.setImageData(imageData)
.setBackgroundHexColor(magHex)
.setBody(text)
.setTitle(title).build(campaignMeta)
FirebaseInAppMessagingDisplay
.getInstance()
.testMessage(this, bannerMessage, null)
}
}
In build.gradle make sure to add:
implementation 'com.google.firebase:firebase-core:17.2.1'
implementation 'com.google.firebase:firebase-inappmessaging-display:19.0.2'
and intent filter into manifest (to process click on the message)
<data android:scheme="app" android:host="open.my.app" />
also modify launchMode to singleTop to process the click within the same instance of the activity:
<activity android:name=".MainActivity"
android:launchMode="singleTop"
>
and apply
apply plugin: 'com.google.gms.google-services'
The result:
Card message:
Banner message and updating text in response to clicking on the banner:
Added project into GitHub if you are interested - project link. Must add your own google-services.json for firebase (to be able to init the engine only)
I got to a project which uses Airbnb DeepLinkDispatch library. This part works fine, I'm able to run activities via URI + query params which parse fine too.
But when I tried using Espresso I got this issue - Intent does not contain Extra with URI and params.
The test I wrote:
class Test {
companion object {
#ClassRule
#JvmField
val rule = ActivityTestRule<MyActivity>(MyActivity::class.java, true, false)
}
#Before
fun setUp() {
val intent: Intent = MyActivity.createIntent(false)
rule.launchActivity(intent)
}
#Test
fun firstTest() {
onView(withId(R.id.switch))
.check(matches(isDisplayed()))
.perform(click())
Screengrab.screenshot("testtest")
}
}
I found out that onCreate in DeepLinkActivity is not called (the class which is annotated with #DeepLinkHandler(AppModule::class).
One way how to fix this could be adding missing EXTRA to the custom intent in setUp(), but this is something I don't want to do. It's a fragile solution and prone to future issues.
Any other ideas how to get espresso + deeplink running together?
Here is the stub of the code.
Click data item on ListView . Works as designed and opens Chrome Custom Tab :
onData(anything()).inAdapterView(withId(R.id.listView))
.atPosition(0).perform(click());
Pause(5000);
Espresso.pressBack();
Cannot seem to evaluate anything in the tab or even hit device back button.
Getting this error
Error : android.support.test.espresso.NoActivityResumedException: No
activities in stage RESUMED.
Did you forget to launch the activity. (test.getActivity() or similar)?
You can use UIAutomator (https://developer.android.com/training/testing/ui-automator.html). You can actually use both Espresso and UIAutomator at the same time. See the accepted answer on the following post for more information:
How to access elements on external website using Espresso
You can prevent opening Custom Tabs and then just assert whether the intent you are launching is correct:
fun stubWebView(uri: String) {
Intents.intending(allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW), IntentMatchers.hasData(uri)))
.respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, null))
}
fun isNavigatedToWebView(uri: String) {
Intents.intended(allOf(IntentMatchers.hasAction(Intent.ACTION_VIEW), IntentMatchers.hasData(uri)))
}
This way you can avoid Espresso.pressBack() in your test.
Note that since these are using Espresso Intents, you need to either use IntentsTestRule or wrap these with Intents.init and release like this
fun intents(func: () -> Unit) {
Intents.init()
try {
func()
} finally {
Intents.release()
}
}
intents {
stubWebView(uri = "https://www.example.com")
doSomethingSuchAsClickingAButton()
isNavigatedToWebView(uri = "https://www.example.com")
}
A suggestion for improved readability following Mustafa answer:
intents{
Intents.intended(
CoreMatchers.allOf(
IntentMatchers.hasAction(Intent.ACTION_VIEW),
IntentMatchers.hasPackage("com.android.chrome")
)
)
}