ShortcutManager crash in instant apps - android

When I run my instant app I'm experiencing a crash due to the shortcut feature, please see the image with the stack trace, this is the only thing I can provide as I'm not able to attach the debugger, another problem :(
**GroupApplication.class**
override fun onCreate() {
...
ShortcutsHelper.init(this)
ShortcutsHelper.addSearchShortcut()
}
object ShortcutsHelper {
lateinit var appContext: Context
lateinit var shortcutManager: ShortcutManager
fun init(context: Context) {
this.appContext = context.applicationContext
this.shortcutManager = context.getSystemService(ShortcutManager::class.java)
}
fun addSearchShortcut() {
val shortcut = ShortcutInfo.Builder(appContext, SEARCH_SHORTCUT_ID)
.setShortLabel(appContext.getString(R.string.search_shortcut_short_label))
.setLongLabel(appContext.getString(R.string.search_shortcut_long_label))
.setIcon(Icon.createWithResource(appContext, R.drawable.ic_search_black))
.setIntent(GroupHomeActivity.getStartIntent(appContext, NavigationScreen.SEARCH))
.build()
shortcutManager.addDynamicShortcuts(listOf(shortcut))
}
I understand that a shortcut does not make any sense in an Instant App, is there a better solution than having all the code related to shortcuts commented when creating an IA?

I understand that a shortcut does not make any sense in an Instant App, is there a better solution than having all the code related to
shortcuts commented when creating an IA?
So yes, Instant Apps is restricted from running some APIs for security purposes. There used to be a FAQ page for it that indicated this, but I can't find it anymore.
But anyways, you don't have to comment it out. You can use:
/instantapps/PackageManagerCompat.html#isInstantApp()

Related

Compose-Espresso link to become idle timed out With JetpackCompose LazyColumn

I am trying to test my lazy column in JetpackCompose and I keep getting this error:
[Compose-Espresso link] to become idle timed out
I tried using composeTestRule.waitforIdle() but it doesn't work. What am I missing here?
#HiltAndroidTest
class MainTest {
#get:Rule(order = 1)
var hiltTestRule = HiltAndroidRule(this)
#get:Rule(order = 2)
var composeTestRule = createAndroidComposeRule<MainActivity>()
private val context = InstrumentationRegistry.getInstrumentation().context
#Before
fun setup() {
hiltTestRule.inject()
composeTestRule.setContent {
HomePage(
context = context,
viewModel = composeTestRule.activity.viewModels<MarvelViewModel>().value,
onClick = {}
)
}
composeTestRule.onRoot().printToLog("currentLabelExists")
}
#Test
fun isResultDisplayedOnLazyColumn() {
composeTestRule.waitForIdle()
composeTestRule.onNode(hasImeAction(ImeAction.Done)).performTextInput("iron man")
composeTestRule.onNode(hasImeAction(ImeAction.Done)).performImeAction()
composeTestRule.onNodeWithTag(TAG_LAZY_COLUMN, useUnmergedTree = true).assertIsDisplayed()
I've got this issue too. The root cause was having my composeview GONE. It seems ComposeTestRule IdlingResource don't like then you call setContent on a Gone ComposeView
After several hours of trials and experimentation, l found a way to fix this issue in a recent project. I would advise to try the following:
Update compose version to 1.2.0-Alpha06 as Skav mentioned. For me that implies bumping my kotlin version to 1.7.0 which my team is not ready for.
So I did some digging around the IdlingRegistry and found that unregistering Espresso-Compose Idling resource at the right spot in the test fixed the issue and my UI test passed afterwards.
IdlingRegistry.getInstance().resources.forEach {
Timber.e("resource ${it.name} idleNow: ${it.isIdleNow}")
if (it.name == "Compose-Espresso link") {
IdlingRegistry.getInstance().unregister(it)
}
}
It is important to note that unregistering Espresso-Compose too early can make your compose ui to fail to display. In my case, l ran this code after the UI is ready before testing for specific UI functionalities.
Building on top of the answer from #leeCoder, I've discovered that upgrading to Compose 1.2.0 (alpha no longer necessary) fixed my problem and there was no need to unregister anything (which was the part that made me the most nervous about his answer).

How to restart Android app within Espresso test?

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)

Why does my app crash when helper class is instantiated?

I'm building a Unity game that uses a native android library with one activity. This library includes bluetooth functionality from an sdk for which i created a Helper class that i can't instantiate or reference in any way in my MainActivity without the game crashing.
I'm new to Kotlin and android studio and can't figure out how to fix this.
Building & running in android and unity works without any errors.
Running an android native app set up in the same way like the android library, works flawless.
But when running the game independantly on phone it crashes, this has been tested on multiple devices.
MainActivity snippet
import com.X.unitylib.BluetoothDeviceHelper //this import is not used for some reason
class MainActivity : UnityPlayerActivity ()/*, BluetoothDeviceHelper.Listener*/ {
private lateinit var bluetoothDeviceHelper: BluetoothDeviceHelper // App runs with this
//private var bluetoothDevices = emptyList<BluetoothDeviceHelper.BluetoothDevice>() //Doesn't run
//private var connectedBluetoothDevice: BluetoothDeviceHelper.BluetoothDevice? = null //Doesn't run
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bluetoothDeviceHelper = BluetoothDeviceHelper(applicationContext) //App doesn't run with this uncommented
}
/* override fun onStart() {
super.onStart()
//bluetoothDeviceHelper.addListener(this)
bluetoothDeviceHelper.startScan()
}*/
**BluetoothDeviceHelper snippet**
class BluetoothDeviceHelper(context: Context) : BLEDevice.Delegate<BLEDevice>, BLEManager.Delegate<BLEObject>
Edit: was able to fix a couple issues so i updated my initial description and replaced the outdated stack trace with the latest error.
Edit2: New error, on monday i'll try the steps described in the link below and once i get it working, i'll post an answer with all the steps i took for others that end up in the same fringe situation.
Kotlin and Unity development error

Dagger2 - Injection in an Android app with Instant App and Dynamic Modules support

I'm trying to setup a boilerplate/proof of concept repository of an Android application that has Instant App and Dynamic Module support, while injecting some third party libraries like Room and Retrofit. It is written in Kotlin. The code can be found at GitHub - Stabber.
I am on the step of adding support for the SplitCompat API so that I can dynamically install Dynamic Modules, as described in App Bundle - Play Core Guide.
In this guide it is described that one can use SplitCompatApplication as their application implementation in their android manifest (and here I am assuming this part goes in the base module's manifest, due to the dependency model of all modules, correct me if I'm wrong).
What I did was follow the section where it is describing what SplitCompatApplication is actually doing, and it is said that it just overrides Application's as stated here:
SplitCompatApplication simply overrides ContextWrapper.attachBaseContext() to include SplitCompat.install(Context applicationContext). If you don’t want your Application class to extend SplitCompatApplication, you can override the attachBaseContext() method manually, as follows:
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
// Emulates installation of future on demand modules using SplitCompat.
SplitCompat.install(this);
}
So I did do exactly this, but with the version that takes into account that I am supporting Instant Apps, so my body of this function override is actually:
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
val isInstant = InstantApps.isInstantApp(this)
if (!isInstant) {
SplitCompat.install(this)
}
}
So apart from the code shown in the previously linked guide, I am just taking out the check and storing it in a variable, which still yields the same result. My problem is that I cannot run my application anymore, as it throws this exception:
Process: app.instant.stabber.app, PID: 6788
java.lang.RuntimeException: Unable to instantiate application app.instant.stabber.android.StabberApplication: java.lang.IllegalStateException: Application context is null!
at android.app.LoadedApk.makeApplication(LoadedApk.java:1017)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5940)
at android.app.ActivityThread.-wrap1(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1755)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6753)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:482)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
Caused by: java.lang.IllegalStateException: Application context is null!
at com.google.android.instantapps.InstantApps.isInstantApp(InstantApps.java:62)
at app.instant.stabber.android.StabberApplication.attachBaseContext(StabberApplication.kt:30)
at android.app.Application.attach(Application.java:218)
at android.app.Instrumentation.newApplication(Instrumentation.java:1107)
at android.app.Instrumentation.newApplication(Instrumentation.java:1091)
I have even tried something like this in order to fix the fact that the Context (the application..?) seems to be Null:
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
this?.let { me ->
val isInstant = InstantApps.isInstantApp(me)
if (!isInstant) {
SplitCompat.install(me)
}
}
}
I really can't figure out where to go from here though. Does anybody have any ideas of what is causing this? The only thing I can think of is that something's wrong with the manifests, because that's what I've struggled with a lot before. The manifests and the fact that the way resources are handled in this modularisation of Android apps is very odd (some string values I've had to put in base instead of instant features, etc).
EDIT: In the current code I am not overriding it manually to accomodate Instant Apps (as suggested by the guide I have linked) and instead I just subclass SplitCompatApplication. It seems to run but I have not yet tested installing any dynamic modules.
EDIT 2: What I came up with in the end, but I want to know why I have to do this in the frist place even though I followed the exact instructions:
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
this.applicationContext?.let { _ ->
val isInstant = InstantApps.isInstantApp(this)
if (!isInstant) {
SplitCompat.install(this)
}
}
}

Is there a way to test Chrome Custom Tabs with Espresso?

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

Categories

Resources