I am trying to create a TWA app in Android Studio that might be working with all our domains and provide seamless navigation between them, but ran out of ideas.
The problem is this - when browsing within one domain all is fine, the "back" button on the phone returns to a previously browsed page. But as soon as you switch to another domain, it looks like the app re-initializes, and the "back" button does not lead back to the previous domain's page. Instead, it minifies the app...
All domains contain Digital Asset files in respective folders, fingerprints are ok, app works perfectly with each individual domain but keeps acting as you've just opened it upon trying to pass from one domain to another via a link.
All domains are added in separate <data> tags under intent-filter with action.view and categories DEFAULT and BROWSABLE in manifest.
Tried adding relations in asset statements to every one of them but no luck getting desired result.
Maybe someone could make it to work? Any advice would be appreciated!
Thanks!
Chunks of my silly newb code
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ru.domain.newsapp">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true">
<meta-data
android:name="asset_statements"
android:resource="#string/asset_statements" />
<activity
android:name="android.support.customtabs.trusted.LauncherActivity"
android:theme="#style/Theme.LauncherActivity"
android:label="#string/app_name">
<meta-data
android:name="android.support.customtabs.trusted.DEFAULT_URL"
android:value="https://sub1.domain.ru/" />
<meta-data
android:name="android.support.customtabs.trusted.STATUS_BAR_COLOR"
android:resource="#color/colorPrimary" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="https" android:host="sub1.domain.ru"/>
<data android:scheme="https" android:host="sub2.domain.ru"/>
<data android:scheme="https" android:host="sub3.domain.ru"/>
</intent-filter>
</activity>
</application>
</manifest>
res\values\strings.xml
<resources>
<string name="app_name">MyApp</string>
<string name="asset_statements">
[{
\"relation\": [\"delegate_permission/common.handle_all_urls\"],
\"target\": {
\"namespace\": \"web\",
\"site\": \"https://sub1.domain.ru\"}
},{
\"relation\": [\"delegate_permission/common.handle_all_urls\"],
\"target\": {
\"namespace\": \"web\",
\"site\": \"https://sub2.domain.ru\"}
},{
\"relation\": [\"delegate_permission/common.handle_all_urls\"],
\"target\": {
\"namespace\": \"web\",
\"site\": \"https://sub3.domain.ru\"}
}]
</string>
</resources>
res\values\styles.xml
<resources>
<!-- Base application theme. -->
<style name="Theme.LauncherActivity" parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:windowAnimationStyle">#null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionModeOverlay">true</item>
<item name="android:windowBackground">#android:color/transparent</item>
<item name="android:backgroundDimEnabled">false</item>
</style>
</resources>
build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "ru.domain.newsapp"
minSdkVersion 16
targetSdkVersion 28
versionCode 2
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
signingConfig signingConfigs.release
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.android.support:appcompat-v7:+'
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.github.GoogleChrome.custom-tabs-client:customtabs:91b4a1270b'
}
UPD
Added code from the files
Also seems like this behavior I was talking about is not a re-init but an overlay with another subdomain content over default that is opened but without action bar like in installed PWA and this leads to the problem that this overlay cannot be closed to return to the previous domain... And I really hoped that this might act like a normal browser tab and open every link in the same window...
Thanks to Google guys problem has been solved.
To be able to open links on other trusted domains inside the TWA app window and as a seamless transition in one tab (without additional overlay) I needed to use android.support.customtabs.trusted.ADDITIONAL_TRUSTED_ORIGINS and pass a string-array to in with required additional trusted domain as well as keeping only main one in intent with
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
There is still a bug that needs to be solved since additional trusted origins still show URL bar but I hope it happens soon.
Reference on this in android-browser-helper's github
https://github.com/GoogleChrome/android-browser-helper/commit/24eefc89e69532d7f57fc83533886c9d762a41eb
https://github.com/GoogleChrome/android-browser-helper/commit/a1507153b420da6a3191fa23c64c5a0c06036e06
UPD
New version of android-browser-helper (1.2.0) finally has this issue fixed and multi-domain now is working as intended (additional trusted origins are verified correctly). Please pay attention to the changes in calling additional trusted origins array as stated in Demo within android-browser-helper.
Related
I had an app that could detect when an android wear device disconnected by using a WearableListenerService and onPeerConnected/onPeerDisconnected.
It seems these have been deprecated, so I am now trying to use onCapabilityChanged, but I cannot get this function called. I use this in my manifest for my service. Documentation on these features are not very good.
<intent-filter>
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
</intent-filter>
So I finally got it to work. It took a combination of things that needed to be set up, but I'll list them all.
The Gradle. You need to make sure the mobile version and the wearable version has the same application id, the same version code, the same version name, and possibly the same play-services version. This is easier to handle if you use the project gradle file to hold these values and have each module reference these values.
In the Root build.gradle file have:
ext {
TARGET_SDK_VERSION = 25
VERSION_CODE = 7
VERSION_NAME = '2.0'
COMPILE_SDK_VERSION = 25
BUILD_TOOLS_VERSION = '25.0.2'
APPLICATION_ID = "com.example.projectname"
PLAY_SERVICES_WEARABLE = 'com.google.android.gms:play-services-wearable:9.4.0'
}
In each of the module build.gradle files, these can be referenced as shown below:
apply plugin: 'com.android.application'
android {
compileSdkVersion rootProject.ext.COMPILE_SDK_VERSION
buildToolsVersion rootProject.ext.BUILD_TOOLS_VERSION
defaultConfig {
applicationId rootProject.ext.APPLICATION_ID
minSdkVersion 20
targetSdkVersion rootProject.ext.TARGET_SDK_VERSION
versionCode rootProject.ext.VERSION_CODE
versionName rootProject.ext.VERSION_NAME
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
provided 'com.google.android.wearable:wearable:2.0.1'
compile 'com.google.android.support:wearable:2.0.1'
compile rootProject.ext.PLAY_SERVICES_WEARABLE
}
The Manifest. With the new updates to the play services, a WearableListenerService must now have an intent-filter defined for each overrided function to be called by the android system. In the case of the onCapabilityChanged function, the intent filter should be defined as:
<service
android:name=".MyWearableListenerService"
android:enabled="true"
android:exported="true" >
<intent-filter>
<action android:name="com.google.android.gms.wearable.CAPABILITY_CHANGED" />
<data android:scheme="wear" android:host="*"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.android.gms.wearable.DATA_CHANGED" />
<action android:name="com.google.android.gms.wearable.MESSAGE_RECEIVED" />
<data android:scheme="wear" android:host="*" android:pathPrefix="/PREF"/>
<data android:scheme="wear" android:host="*" android:pathPrefix="/start"/>
</intent-filter>
</service>
The intent-filter for onCapabilityChanged is com.google.android.gms.wearable.CAPABILITY_CHANGED. Along with that, the intent-filter also needs to be told the data scheme and host. This can simply be data android:scheme="wear" android:host="*". The pathPrefix can be omitted for this intent-filter. Notice that the intent-filter for the com.google.android.gms.wearable.DATA_CHANGED and com.google.android.gms.wearable.MESSAGE_RECEIVED needs the pathPrefix defined to be able to have their respective functions called in the service.
The capability file. In order for the onCapabilityChanged function to launch, the system needs to detect a device with a capability being connected. To do this, we must have the capability defined in an xml file in each module.
To do this, in each module, save a file named wear.xml in the res/values directory. The file must have a string array named android_wear_capabilities with items that describe the capabilities you wish your module to advertise to another device. Below is an example of a wear.xml file included in a wearable module.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="android_wear_capabilities">
<item>verify_remote_wear_app</item>
</string-array>
</resources>
First, It is important to note that the file must be named wear.xml and must be placed in the values directory. Secondly, the string-array must be named android_wear_capabilities. Also make sure that every capability in each module has a unique name.
If any of the above is not correct, then the onCapabilityChanged function will never be called, and you will be pulling your hair out in frustration.
Now, to actually tell if a device was disconnected, use the onCapabilityChanged function:
public void onCapabilityChanged(CapabilityInfo capabilityInfo) {
super.onCapabilityChanged(capabilityInfo);
if(capabilityInfo.getNodes().size() > 0){
Log.d(TAG, "Device Connected");
}else{
Log.d(TAG, "No Devices");
}
}
This function will tell you when a device has connected or disconnected, assuming only 1 device is connected at a time.
I currently have a project that needs to contain two different forms of codebase, legacy and an updated version of the application. I am using Flavors for this, but am running into an issue where two app icons are being installed. The reason is because both the legacy codebase and the updated codebase have their own manifest.xml, and inside the manifest are declarations for identifying the Main launch class and their relative app icon.
<!-- legacy code manifest -->
<activity
android:name="legacy.activity.RegistrationActivity"
android:configChanges="orientation|screenSize"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:theme="#style/CustomAppTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- updated code manifest -->
<activity
android:name="updated.activity.RegistrationActivity"
android:configChanges="orientation|screenSize"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:theme="#style/CustomAppTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
How can I get around this? If I remove the intent-filter from the updated code base, which is the Main codebase, the Flavor will not install two icons. However, I am unable to run the Main codebase because I have not declared an entry point in my Manifest. And conversely, if I remove the intent-filter from the Flavor and keep it in my Main codebase, the Flavor code will not run. The two RegistrationActivity classes are different, just with the same name. The Legacy code does not really share much of the updated codebase. Any suggestions other than separate into different projects?
Some have requested my setup with Flavors in gradle, here is snapshot of it.
productFlavors {
standard {
applicationId 'updated.android.example'
manifestPlaceholders = [package_name: "updated.android.example", primary_lang: "en"]
signingConfig signingConfigs.keystore
}
legacyTest {
applicationId 'legacy.android.example.debug'
manifestPlaceholders = [package_name: "legacy.android.example.debug",
target : "Test", primary_lang: "en"]
signingConfig signingConfigs.keystore
}
legacyProd {
applicationId 'legacy.android.example.prod'
manifestPlaceholders = [package_name: "legacy.android.example.prod",
target : "Prod", primary_lang: "en"]
signingConfig signingConfigs.keystore
}
This line:
<category android:name="android.intent.category.LAUNCHER"/>
says that you have a launching activity. Set them to a default activity setting(this is just "another activity", create a third class that is the launcher-class. This class will automatically redirect to one of the two other activities based on whatever specifications you may have, such as API level or brand. Any specifications you have as to which to launch, set them and the user will not know there is a handling activity.
When you have two launcher activities, they show as two applications. This is probably because the system cannot determine what to redirect to automatically. So there are two depending on what to launch.
How can I get around this? If I remove the intent-filter from the updated code base, which is the Main codebase,
Don't remove it. Change it. E.g.:
<intent-filter>
<action android:name="com.yourpackage.name.CLASSNAME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
You need separate <activity android:name="legacy.activity.RegistrationActivity" and <activity android:name="updated.activity.RegistrationActivity" you two manifests, one for each flavor.
eg.:
src/main/AndroidManifest.xml (with everything except: *.activity.RegistrationActivity)
src/legacy/AndroidManifest.xml (with legacy.activity.RegistrationActivity)
src/updated/AndroidManifest.xml (with updated.activity.RegistrationActivity)
In recent versions of the Spotify Android app (3.9.0.965 at the time of writing) the Share -> Send to menu shows a bespoke list of options:
Select Recipient, Email, SMS and then a list of other apps (WhatsApp, Hangouts etc).
Is it possible for me to get my app onto that list? I'd like to be able to share a Spotify track with my app and play it.
Is it possible for me to get my app onto that list?
No, unfortunately this is not possible, even if your manifest is properly configured you cannot see your app when you choose Share -> Send to because Spotify will display only a predefined set of apps (WhatsApp, Facebook Messenger, Hangouts).
For example we have an app with the package name com.example.spotify. Add this intent-filter to the AndroidManifest.xml:
<intent-filter>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="text/plain"/>
</intent-filter>
Run the app, but if we choose Share -> Send to the app will not appear.
Now change the applicationId to one of the whitelisted package name (com.whatsapp, com.facebook.orca, com.google.android.talk) in our build.gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
applicationId "com.whatsapp"
minSdkVersion 15
targetSdkVersion 23
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Now the application is available in the Share -> Send to context menu as if it were WhatsApp, as you can see in this screenshot:
Choosing WhatsApp our app will correctly opens and receive the intent from Spotify.
You need provide the Activity(SomeShareActivity) in the manifest and provide the IntentFilters to it
<activity android:name=".SomeShareActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="audio/*" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
Is it possible to replace the activity that gets the intent-filter
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
by configuring flavors?
There are a number of ways to accomplish this thanks to the manifest merger.
The easiest way is to use a placeholder in your manifest and define the appropriate class in your build.gradle.
For example, in your manifest:
<activity android:name="${launchActivityName}">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
And in your build.gradle:
productFlavors {
flavor1 {
manifestPlaceholders = [ launchActivityName:"com.example.MainActivity"]
}
}
You can also include a different manifest file with each flavor.
Yes, just add a manifest file in the folder of the product flavour and
The Manifest of the product flavor is merged on top of the Manifest of the main configuration.
More info here: http://tools.android.com/tech-docs/new-build-system/build-system-concepts
In addition to #Tanis answer, I want to add some parts you should aware of.
productFlavors {
lite {
dimension "default"
applicationId "com.foo.bar"
manifestPlaceholders = [ applicationLabel:"#string/app_name_foo"
, launcherAct: "com.foo.qux.activity.Hello"
, launcherAct2: "com.foo.qux.activity.World"]
}
full {
dimension "default"
applicationId "com.foo.qux"
manifestPlaceholders = [ applicationLabel:"#string/app_name_bar"
, launcherAct: ".activity.World"
, launcherAct2: ".activity.Hello"]
}
}
If let's say, your World.java exists in com.foo.qux.activity folder, then you should use full path com.foo.qux.activity.World instead of .activity.World when put it inside applicationId "com.foo.bar" flavor, or else it will auto prepend to com.foo.bar.activity.World which the file doesn't exist.
Unlike android:label, you can't use something like #string/launcher_class in android:name, or else you will get error: attribute 'android:name' in <activity> tag must be a valid Java class name. when build.
Ensures you don't duplicate the class, e.g. :
<activity
android:name="${launcherAct}"
android:label="${applicationLabel}">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="${launcherAct2}"
android:label="${applicationLabel}">
</activity>
Don't forgot to change from .activity.World to android:name="${launcherAct2}" or else you end up duplicated activity (i.e. one in AndroidManifest.xml, the other one in manifestPlaceholders).
The Unresolved class red indicator can safely ignored, seems like Android Studio bug:
I make a bug report here.
I'm sure I'll catch some grief for lack of specificity or asking a question that's been asked, but this truly is a last option and I've really tried too many ways to explain them all. Here's my issue:
I've been going through the standard tutorials provided by developers.android.com and I've made it to the Styling of the ActionBar with pretty much no issues. Well now I'm paying for those lack of issues and I really cannot get any sort of custom theme to work on the action bar.
FACTS:
I am using the AppCompat-V7 as my app will be quite simple and ideally compatible with as many versions as possible.
I CAN change the basic themes (Dark, Light, and Light.DarkActionBar)
I've attempted the Android Action Bar Style Generator and followed this turorial here (http://www.androiduipatterns.com/2012/09/creating-custom-android-styles-easy-way.html)
I've watched a handful of videos that somewhat related to my issue/settings to try and get it right.
I've tried various methods (all wrong) to get this to work, some of which probably don't even make sense.
So... how about it?
Here are my files that I believe are relevant, but if anyone wants to see more, feel free to ask. Considering I'm hardly through the Android tutorials, I don't have much to look at, so I suppose that's good.
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="godwin.com.study" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<application
android:allowBackup="true"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/CustomActionBarTheme" >
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".DisplayMessageActivity"
android:label="#string/title_activity_display_message"
android:parentActivityName=".MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="godwin.com.study.MainActivity" />
</activity>
</application>
</manifest>
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
</style>
</resources>
themes.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- the theme applied to the application or activity -->
<style name="CustomActionBarTheme"
parent="#style/Theme.AppCompat.Light.DarkActionBar">
<!-- Support library compatibility -->
<item name="actionBarStyle">#style/MyActionBar</item>
</style>
<!-- ActionBar styles -->
<style name="MyActionBar"
parent="#style/Widget.AppCompat.Light.ActionBar.Solid.Inverse">
<!-- Support library compatibility -->
<item name="background">#drawable/actionbar_background</item>
</style>
</resources>
build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 21
buildToolsVersion "21.1.2"
defaultConfig {
applicationId "godwin.com.study"
minSdkVersion 8
targetSdkVersion 18
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile "com.android.support:appcompat-v7:18.0+"
compile fileTree(dir: 'libs', include: ['*.jar'])
}
Feel free to ask questions, but be prepared for "I have no idea". I'm pretty much lost. And yes, I did remove a lot of my attempts. They overlapped and interfered with each other, so what I have here in my files does work, except for the line:
<item name="background">#drawable/actionbar_background</item>
in the themes.xml file. I have put my "actionbar_background.png" files into the "drawable" folder, but it still only produces the "...Light.DarkActionBar" theme.
I really hope it's something simple, but through all my searching I've yet to be able to find the answer.
ON A SIDE NOTE: This is my first time developing an Android App, or at least trying to, and I really don't think Android did a good job with their tutorials. I mean, I'm decent at programming. Been working with C++ mostly, but also some Java, Javascript, and web design languages, and I get them for the most part. Is it just me? or is getting into the world of app development really dang difficult?
EDIT: To try and put it simply, I've tried many methods to change the looks of the actionbar with a custom theme (whether it's using color codes or images/.pngs), but the only way I can get it to change is by the use of the preset themes (Light, Dark, and Light.DarkActionBar).
I would like to be able to use something like the Android Action Bar Style Generator that I mentioned,
As is noted on the site, that generator is deprecated, because you do not need it with the AppCompat action bar. If your objective is to provide a custom background color to your action bar, and if you are on the current version of AppCompat (appcompat-v7, version 21 or higher), you do that via colorPrimary.
For example, this sample app sets a custom color on the action bar. It has a res/values/colors.xml that defines some colors:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="primary">#3f51b5</color>
<color name="primary_dark">#1a237e</color>
<color name="accent">#ffee58</color>
</resources>
It has a res/values/styles.xml that applies the primary color to the colorPrimary theme attribute:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.Apptheme" parent="Theme.AppCompat">
<item name="colorPrimary">#color/primary</item>
<item name="colorPrimaryDark">#color/primary_dark</item>
<item name="colorAccent">#color/accent</item>
</style>
</resources>
It applies the custom theme in the manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.commonsware.android.abverscolor"
android:versionCode="1"
android:versionName="1.0">
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"/>
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="19"/>
<application
android:allowBackup="false"
android:icon="#drawable/ic_launcher"
android:label="#string/app_name"
android:theme="#style/Theme.Apptheme">
<activity
android:name="ActionBarDemoActivity"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
You get: