Maps Android API doesn't work offline without online initialization - android

I'm using Maps Android API in the offline mode extensively in travel apps I build. On the first app launch, I download all the tiles I need so that they're available later in the field without the Internet. Everything works great, but I noticed that the Maps API does require Internet connection on its first use after app installation. The framework probably performs API key validation to make sure it's legit.
Since my fragments containing com.google.android.gms.maps.MapView are not displayed on the first screen, there's a risk a user downloads the map for offline use in a hotel, goes into the wild, and... kaboom! - map is not displayed.
How to initialize Android Map framework so that maps are available later when there's no connection? Is there a way to skip online key validation?

After some experimenting I found out a simple solution.
So, first, in my first activity layout (it's a host activity for all my fragments) I added the following zero-sized invisible MapView:
<com.google.android.gms.maps.MapView
android:id="#+id/dummyMapViewToInitForOfflineUse"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="invisible"
/>
Then, in the activity code, I added the following method:
private void initGoogleMapFrameworkToMakeItUsableOfflineLater() {
dummyMapViewToInitForOfflineUse.onCreate(new Bundle());
dummyMapViewToInitForOfflineUse.getMapAsync(ignored -> {
Timber.d("GoogleMap framework initialized and ready to use offline later");
});
}
You can call it in onCreate as well as at any other reasonable moment (I use AndroidAnnotations, so I called it from my init method annotated with #AfterViews). Obvoiusly, if you don't use AndroidAnnotations or other view binding framework, you need to perform findViewById(R.id.dummyMapViewToInitForOfflineUse).

If you are aiming at caching google map's tiles for offline use then you may be violating their terms,You are first required to purchase their enterprise Maps API Premier, check this link How to cache Google map tiles for offline usage?

Related

Android Google Maps Offline - my location is not shown

I am trying to use Google Maps for Android, offline (always and forever).
Surprisingly, I can't find any question here that asks or solves this issue specifically.
When I use a new offline phone, both my app and Google Maps show a blank map (dah, no map loaded) and 'my' location blue dot is not shown. Well, actually, no marker is shown.
To Reproduce
Restore any Android phone to its factory settings
Enable location services (GPS, without connecting to the internet at any stage)
Open the Google Maps app
--> See that there is no 'my location' blue marker, although when you long click on the screen, the app shows its coordinates (meaning, GPS does work, but the map doesn't show it)
Technical Symptoms
Even when I load offline maps (.mbtiles format, custom ones, not Google's) they're still not shown (nor the markers). It's like Google put some code like this:
if (no internet) hideAllViews().
Note that once I connect the phone to the internet, our custom tiles do work, even if I later turn the phone offline.
I can interact with the map (long click to view the clicked location, for example, which shows that my GPS location indeed works), but that's about it (until I connect the phone to the internet, from which point I can turn it offline again but with everything surprisingly working).
Code Example - a simplified version
//build.gradle:
implementation 'com.google.android.gms:play-services-maps:17.0.0'
//MapActivity.kt
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.MapView
import com.google.android.gms.maps.MapsInitializer
class MapActivity : AppCompatActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.map_activity)
val mapFragment = supportFragmentManager.findFragmentByTag(MAP_FRAGMENT_TAG) as MapFragment?
?: MapFragment().also {
supportFragmentManager.beginTransaction().add(R.id.map, it, MAP_FRAGMENT_TAG).commit()
}
mapFragment.getMapAsync(::onMapReady)
mapFragment.retainInstance = true
}
private fun onMapReady(map: GoogleMap) {
map.isMyLocationEnabled = true
map.uiSettings.isMyLocationButtonEnabled = true
Log.d("GoogleMap", "Map should be ready and visible with my-location marker shown, if phone's GPS is enabled")
}
}
I hope someone here knows a trick or worked at Google and can shed some light on this.
Thank you!
As opposed to most similar questions, I do know how to make this work (internet...) but am asking specifically about a use case where a new phone can never be connected to the internet - not even one time for one second.
I am familiar with other offline maps services, but am trying to solve this with Google's maps, at least for now
It is not possible to load the API without connecting to the internet first since it was designed to be used online(As of now), so this is Working As Intended.
Please note that using Maps SDK for android requires an internet connection first to load because it checks the API key. Then you can use the Map offline for a certain period of time(there's no definite period of time for offline functionality that requires you to be online again)
But there are customers who are also interested in this functionality, so there is an ongoing entry for it in the Google Issue Tracker that was created since 2013 to let the API users be aware of this feature request.
You can view and star the feature request here:
https://issuetracker.google.com/35823181
Please note that the Issue Tracker entry above is the authoritative source for public information regarding the aforementioned feature requests, and all publicly-relevant updates will be posted there.

How to use Google Maps API offline in Angular/Ionic App

I have an Angular 12 Ionic app that is using the Google Maps API via #angular/google-maps package (https://github.com/angular/components/tree/master/src/google-maps#readme). The application we are building will be used in areas where internet access can be spotty at best. We are attempting to maintain functionality of the map when internet is lost. IE, potentially cache or store the loaded map locally to reference should we go offline.
We are expecting the user to be online when we initialize the map and load any polylines & markers
Should the user then go offline, and navigate around the app, when coming back to the map we would like to show the map, polylines and markers that were previously loaded.
What currently is happening is the the component is being destroyed and then reloaded when navigated back causing a reload of the map which cannot happen because we are offline. However, if we were able to cache or locally store that map, then we can load it from the cache/local until the user comes back online.
Using the package described above we are looking at trying to serialize the google.maps object to json for storage, however it is causing an issue.
let mapTest: google.maps.Map;
const center: google.maps.LatLngLiteral = { lat: centerPoint.lat, lng: centerPoint.lng };
mapTest = new google.maps.Map(document.getElementById('map') as HTMLElement, {
center,
zoom: 11
});
This is a basic typescript implementation as provided by the Google API Documentation (https://developers.google.com/maps/documentation/javascript/using-typescript). When we attempt to stringify the map we get the following error
Same error shows if we stringify mapTest.data.
Question is, is there anything we can do to locally store the google.map object or the google.map.data object? This ultimately will be put onto an Android device using Ionic, is something like this possible with Native Android maps? Or is this just not a feasible task at all
The Google Maps JavaScript API cannot be used offline.
See feature request: API feature to download a map for offline use.

MapSettings.setIsolatedDiskCacheRootPath() is unclear

I'm assessing HERE Android SDK Premium v3.13.2_86.
In the turn-by-turn-navigation sample there is a call MapSettings.setIsolatedDiskCacheRootPath(). However the API doc says the method is deprecated:
Deprecated. As of SDK 3.13. This method will be replaced with new one once shared map service feature is removed.
Method to allow switching of the disk cache to a separate service process and disk cache path.
So I am not sure whether I should use this method or not. What happens if I don't call it?
Of course, I'd like to have a map data cache in my app. And ideally it should be private to my app, so other apps (even those using HERE SDK) do not have access to the cache. How can I achieve this?
From the documentation and from the except you quoted, SDK 3.13 still supports shared map service so you have to keep using the MapSettings.setIsolatedDiskCacheRootPath() method until in a later release when shared map service feature is removed and a new method introduced.
With SDK 3.16 it is proposed to use setDiskCacheRootPath instead
val diskCacheRoot = (Environment.getExternalStorageDirectory().path
+ File.separator + ".isolated-here-maps")
MapSettings.setDiskCacheRootPath(diskCacheRoot)

Firebase Dynamic Links - Can't get Url in android after install app from play store

If I install the app when clicking the dynamic link. All of that information from dynamic should be still available when I open the app for the first time.How can I get that information? It is not working when I use this: getInitialLink() returns Promise<string|null>;
Since, you haven't mentioned - I'm assuming you are having problems with shorter urls, if that's the case try putting the longer url.
Or refer here on Simon's answer: When I use the long instead of short links, everything works perfectly fine.
On Android, you use the getInvitation() method to get data from the Dynamic Link:
AppInvite.AppInviteApi.getInvitation(mGoogleApiClient, this, false).setResultCallback
(/* ... */);
Then, in the callback, you can get the data passed in the Dynamic Links link parameter by calling the getDeepLink() method:
Firebase Documentation - Use Case
For future reference or detailed answer on Firebase Dynamic Links
Behave just like normal Links
In cases where the application doesn’t require installation (say, if it’s already installed) then clicking the Dynamic Link will automatically open the link to the desired screen.
Dynamic Links have a very simple process flow:
The user begins by clicking the Dynamic Link
If the the needs of the Dynamic Link target are satisfied (this is, the application being installed) then the user is navigated to the target location
Otherwise, if the application requires install in order to navigate
to the Dynamic Link target, the the user is taken to the point of
install for the application. Once the application has been installed,
the user is navigated to the target location of the Dynamic Link
And if that wasn’t all, we can integrate Dynamic Links with Firebase Analytics to track the interaction with any links that we generate for our applications. But if we only require simple tracking, then we can use the automatic built-in analytics from the Dynamic Links panel within the Firebase Console where we can also obtain attribution and referrer information for interacted links with no extra effort required from our side.
What makes it different from Google Analytics?
One of the first things that came to my mind when I read about Firebase Analytics was, “What about my Google Analytics setup?”. So if you already have Google Analytics in place, then why would you make the switch to Firebase Analytics? Well, here’s a couple of differences between the two:
Audiences
We can use Firebase Analytics to create Audiences — these are groups of users that we can then interact with using other Firebase service such as Firebase Notifications and / or Firebase Remote Config.
Integration with other Firebase Services
An awesome thing with Firebase Analytics is that we can integrate other Firebase services with analytics. For example, creating an Audience of users who have experienced a crash reported through Firebase Crash Reporting.
Lower Method Count
The Google Analytics dependency on Android has a total count of 18,607 methods and has a total of 4kb used for dependancies. On the other hand, Firebase Core (for Analytics) has a method count of 15,130 and only 1kb used for dependancies.
Automatic Tracking
When we add the firebase core dependency, it will automatically begin tracking a collection of user engagement events and device information for us — this is useful if you’re looking to only collect the minimal data for your app.
Unlimited Reporting
For up to 500 events, Firebase Analytics provides us with unlimited reporting straight out of the box for free!
No Singleton Initialisation
When setting up Google Analytics on Android we are required to initialize a Singleton instance. Firebase Analytics are simply available by fetching the instance directly from where we wish to track data. This isn’t much effort obviously but just makes the setup flow slightly easier.
Single Console
All of the data for every Firebase service is available for a single console. That makes it both easier and quicker for us to navigate from checking the analytic stats for our app to viewing the latest crash reports.
It looks like this is a react-native-firebase open bug for android
For fix the only thing that is required to be changed in module code:
private boolean isInvitation(PendingDynamicLinkData pendingDynamicLinkData) {
return FirebaseAppInvite.getInvitation(pendingDynamicLinkData) != null;
}
to
private boolean isInvitation(PendingDynamicLinkData pendingDynamicLinkData) {
FirebaseAppInvite invite = FirebaseAppInvite.getInvitation(pendingDynamicLinkData);
if (invite != null && invite.getInvitationId() != null && !invite.getInvitationId().isEmpty()) {
return true;
}
return false;
}
Bug reference : https://github.com/invertase/react-native-firebase/issues/1273
Please Check Your Manifest file
open AndroidManifest.file => In your activity tag there is intent-filter tag put below line in that tag.
<data android:scheme="https" android:host="your.dynamic.link" />
<data android:scheme="http" android:host="your.dynamic.link" />
If already done then check this link for the full blog on the dynamic link with react native.
Link: http://blog.logicwind.com/react-native-dynamic-links-using-firebase/
I hope this will help. sorry for the typos.

How to use AdWhirl with an unsupported ad company?

Does anyone know how AdWhirl works?
I set up my custom event for Greystripe in which I initialize the SDK if it wasn't already initialized, and refresh the BannerView, but I don't see the custom event getting called. So my main question is, how and when does AdWhirl call the custom event? What are the rations and rollovers? I haven't done anything with them (mainly because I don't know why I need them. What does AdWhirl do with them?)
Also, how do I control when AdWhirl refreshes my banner? I'd like to tie the refresh with a button action.
I've been searching online nonstop for the past two days and read a lot of tutorials and example Java classes that people have shared, but none of them have worked. It just looks like AdWhirl is stagnate. It's so unclear to me how AdWhirl works beyond: it mediates between the app and all the ad opportunities you want to use in your ad. That's an entirely too high-level understanding for me to move forward. :(
Have you read the wiki page that describes how to use Custom Events? You basically create a custom event in the backend UI which behaves like another ad network, and you can configure it's traffic. Then you can implement the function name that you named in the backend. The only unintuitive part is that you have to implement AdWhirlInterface to listen for the custom event, which means creating an adWhirlGeneric() method. This method can be empty though, I am not seeing it called when creating my own test event. Finally, make sure to set the AdWhirlInterface.
So assuming on the backend you created a network with:
Name: Test Network
Function Name: testEvent
and gave it traffic (I recommend giving it 100% traffic when testing), then your code would look something like this:
public class MyActivity extends Activity {
...
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
...
}
...
public void testEvent() {
// Place event code here.
Log.d("Cust_Network", "Cust network got called!");
}
}
To control refreshing your ad, call rotateThreadedNow() on the AdWhirlLayout when a button is clicked, for example. This will take AdWhirl through the process of randomly determining a new ad network, and calling the correct adapter, or custom event in this case. If you choose to go this route, you may not want automatic refreshing, in which case you should disable automatic refreshing on the back end.
The ration object is populated with data from the configuration data. Each ration represents an ad network, and has keys which represent the individual ad network ids, weight percentages that you set in the backend, and backfill priority. Backfill priority is the network order that AdWhirl will request from if the original request did not fill. This process of going through backfill priority is called rollover. You will need to know a little bit about rollover when implementing your own custom event.
The wiki page mentioned has these recommendations to add to your custom event:
// In your custom event code, you'll want to call some of the below methods.
//
// On success:
// this.adWhirlLayout.adWhirlManager.resetRollover();
// this.adWhirlLayout.rotateThreadedDelayed();
//
// On failure:
// this.adWhirlLayout.rolloverThreaded();
If your custom event properly fetches an ad, you will want to reset the rollover order (so the next request will have the correct backfill order), and call rotateThreadedDelayed() so that a refresh will happen automatically in the amount of time you specified on the back end. If the ad request failed, you will want to call rolloverThreaded() so that AdWhirl can go through it's rollover process to check your other configured ad networks for ads.
if you want you can use an open source library i've developed that allows to use AdWhirl with other (unsupported) ad networks (but also with the officially supported ones). This library is also extensible, so you can add a new network to it and manage easily through AdWhirl.
The library is AdMAL (Ad Mediation Abstraction Layer) and is available on a github.com repository under the Apache 2.0 open source license: https://github.com/marcosiino/AdMAL
Using AdMAL you can easily implement AdWhirl in your applications for both supported and unsupported network (the integration is easiest than implementing AdWhirl SDK). Actually it only supports iOS (it's developed in Objective-C), but i plan to port to android in the next months.
I started AdMAL for my own purposes, then decided to release it to the public under an open source license some days ago. Hope this help! I encourage other developers to improve the library and implement new networks support, so that this can benefit to everyone.

Categories

Resources