# flutter\_map\_tile\_caching

A plugin for 'flutter\_map' providing advanced offline functionality

[![pub.dev](https://img.shields.io/pub/v/flutter_map_tile_caching.svg?label=Latest+Version)](https://pub.dev/packages/flutter_map_tile_caching) [![stars](https://badgen.net/github/stars/JaffaKetchup/flutter_map_tile_caching?label=stars\&color=green\&icon=github)](https://github.com/JaffaKetchup/flutter_map_tile_caching/stargazers) [![likes](https://img.shields.io/pub/likes/flutter_map_tile_caching?logo=flutter)](https://pub.dev/packages/flutter_map_tile_caching/score)        [![Open Issues](https://badgen.net/github/open-issues/JaffaKetchup/flutter_map_tile_caching?label=Open+Issues\&color=green)](https://github.com/JaffaKetchup/flutter_map_tile_caching/issues) [![Open PRs](https://badgen.net/github/open-prs/JaffaKetchup/flutter_map_tile_caching?label=Open+PRs\&color=green)](https://github.com/JaffaKetchup/flutter_map_tile_caching/pulls)

{% hint style="success" %}
**Welcome to v10**

If you're coming from v9, it might help to see the [migration info](/get-started/v9-v10-migration). Or, if you'd like to stay on v9 for the time being, that [documentation is still available](https://fmtc.jaffaketchup.dev/v9/)!
{% endhint %}

## Highlights

<table data-card-size="large" data-view="cards" data-full-width="true"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><p><mark style="color:blue;">◉</mark> 📲</p><p><strong>Integrated Caching × Bulk Downloading</strong></p></td><td>Get both dynamic browse caching that works automatically as the user browses the map, and bulk downloading to preload regions onto the user's device, all in one convenient, integrated API!</td><td><ul><li><a data-footnote-ref href="#user-content-fn-1">Multi-cache ('store') support</a> with <a data-footnote-ref href="#user-content-fn-2">minimized tile duplication</a> and <a data-footnote-ref href="#user-content-fn-3">maximum flexibility</a></li><li><a data-footnote-ref href="#user-content-fn-4">Download any shape of area</a></li><li><a data-footnote-ref href="#user-content-fn-5">Automatic sea tile skipping</a> when bulk downloading</li><li><a data-footnote-ref href="#user-content-fn-6">Optional download rate limiting</a></li><li>Recoverable failed bulk downloads</li></ul></td><td></td></tr><tr><td><p><mark style="color:red;">◉</mark> 🏃</p><p><strong>Ultra-fast &#x26; Performant</strong></p></td><td>No need to bore your users to death anymore! Bulk downloading is super-fast, and can even reach speeds of over 1000 tiles per second<a data-footnote-ref href="#user-content-fn-7">*</a>. Existing cached tiles can be displayed on the map almost instantly.</td><td><ul><li>Multi-threaded setup to minimize load on main thread, even when browse caching</li><li>Streamlined internals to reduce memory consumption</li><li>Successfully downloaded tiles aren't re-downloaded when an unexpectedly failed download is recovered</li></ul></td><td></td></tr><tr><td><p><mark style="color:green;">◉</mark> 🧩</p><p><strong>Import &#x26; Export</strong></p></td><td>Export and share stores, then import them later, or on other devices! You could even remote control your organization's devices, by pushing tiles to them, keeping your tile requests (&#x26; costs) low!</td><td></td><td></td></tr><tr><td><p><mark style="color:purple;">◉</mark> 💖</p><p><strong>Quick To Implement &#x26; Easy To Experiment</strong></p></td><td>A basic caching implementation can be setup in four quick steps, and shouldn't even take 5 minutes to set-up. Check out our <a data-mention href="/pages/ZLHYkAJpLD5h8yB2SNyt">/pages/ZLHYkAJpLD5h8yB2SNyt</a> instructions.</td><td>Ready to experiment with bulk downloading, but don't want to make costly and slow tile requests? Check out the testing tile server included in the FMTC project: <a data-mention href="/pages/MwEjeVx1V910UW5bCitN">/pages/MwEjeVx1V910UW5bCitN</a>!</td><td></td></tr></tbody></table>

### Trusted by many

In addition to our generous [supporters](/supporters), FMTC is also trusted and [used by businesses](#user-content-fn-8)[^8] all around the world. Here's just a few!

<table data-card-size="large" data-view="cards"><thead><tr><th align="center"></th><th><select><option value="Zc98uj3O3XSR" label="Paid alternative license" color="blue"></option><option value="OXddfK5XeRk1" label="Free alternative license (non-GPL open-source)" color="blue"></option></select></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td align="center">Lafayette GPS</td><td><span data-option="Zc98uj3O3XSR">Paid alternative license</span></td><td><a href="/files/dTfr056w1lZzLW3ntbP7">/files/dTfr056w1lZzLW3ntbP7</a></td><td><a href="https://gps.lafayette.se/">https://gps.lafayette.se/</a></td></tr><tr><td align="center"><strong>P</strong>itchero GPS</td><td><span data-option="Zc98uj3O3XSR">Paid alternative license</span></td><td><a href="/files/Lj7B7oc9iqnvyLOfgB8h">/files/Lj7B7oc9iqnvyLOfgB8h</a></td><td><a href="https://www.pitcherogps.com ">https://www.pitcherogps.com </a></td></tr><tr><td align="center">nventive</td><td><span data-option="Zc98uj3O3XSR">Paid alternative license</span></td><td><a href="/files/GQN0nwXPzkTocH6Qxeaj">/files/GQN0nwXPzkTocH6Qxeaj</a></td><td><a href="https://nventive.com/">https://nventive.com/</a></td></tr><tr><td align="center">wemove digital solutions GmbH (contracting for Lower Saxony Ministry for the Environment, Energy and Climate Protection)</td><td><span data-option="OXddfK5XeRk1">Free alternative license (non-GPL open-source)</span></td><td><a href="/files/NFkvzHSDGW1S6bKJLNx3">/files/NFkvzHSDGW1S6bKJLNx3</a></td><td><a href="https://opencode.de/de/software/umwelt-navi-4272">https://opencode.de/de/software/umwelt-navi-4272</a></td></tr></tbody></table>

### How can FMTC elevate my app to the next level?

Easy! Take a look at Google Maps, or Strava, or whichever other app of your choice.

<details>

<summary>More than just preset rectangles</summary>

Whether it's walking along a remote winding river using the Line region, downloading all of central London ready for that weekend exploration using the Circle region (roaming fees + maps gets expensive fast!), or tracking your belongings across a vast, shapeless space using the Custom Polygon region, FMTC has your user's back - but not all of their storage space!

</details>

<details>

<summary>Keep storage usage efficient</summary>

With Sea Tile Skipping, you can avoid storing unnecessary tiles of pure sea, then use the map client's functionality to just paint the spaces the same color as the sea.

Highly flexible stores allow for multiple stores to be used to more efficiently store and control areas, and tiles can be used by multiple stores without duplication.

Import/export functionality allows users to temporarily offload stores to another device to keep their device lightweight until they require those tiles, without having to download them from the origin server again.

***Raster tiles do consume more storage than vector tiles.** Vector tile support is planned and already possible with some custom integration.*

</details>

<details>

<summary>Highly controllable and flexible downloads</summary>

Bulk downloads can be paused and resumed at any time, and with download recovery, downloads that stopped unexpectedly can be started right from where they left off, without your user even knowing something went wrong.

</details>

<details>

<summary>I wonder how much it costs the app developers?</summary>

FMTC supports bulk downloading from any tile server\*[^9], so you can choose whichever one suits you best.

Our browse caching mechanism doesn't result in any extra requests to the tile server, and in fact can reduce costs by serving tiles to users from their local cache without cost. Or, if you're running your own server, you can reduce the strain on it, keeping it snappy with fewer resources!

Downloads can be rate limited to avoid running up to the server's rate limit or excess fee.

And with export/import functionality, user's can download regions just once, then keep the download themselves for another time. Or, you can provide a bundle of tiles to all your user's, while still allow it to be updated per-user in future!\*[^10]

For (proprietary) licensing information, as FMTC is licensed under GPL-v3, please see [(Proprietary) Licensing](/proprietary-licensing).

</details>

***

## Supporting Me

This project is wholly open source and funded by generous supporters like you! Any amount you can spare that you think FMTC deserves is hugely appreciated, and means a lot to me :)

{% content-ref url="/pages/yhLUvZi1rccWYQwir6gB" %}
[Supporters](/supporters)
{% endcontent-ref %}

## (Proprietary) Licensing

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution.
{% endhint %}

{% content-ref url="/pages/Qr8MaFUrPCkW89RA1sBp" %}
[(Proprietary) Licensing](/proprietary-licensing)
{% endcontent-ref %}

***

{% hint style="warning" %}
ObjectBox has a complex license model - the build time dependency is open-source, whilst the native library *runtime only* dependency is under a closed-source (but relatively relaxed) [license](https://objectbox.io/0209-ob-binary-license/) (that is liable to change at ObjectBox's will).

This is not an issue for the majority of applications. However, ObjectBox is known to be (rightly or wrongly) banned as a dependency from apps on F-Droid (last checked September 2024).

Future updates to FMTC will implement alternative backends using other libraries, and the default/preferred backend may indeed change in future.

For more information, please see: <https://github.com/JaffaKetchup/flutter_map_tile_caching/issues/167>.
{% endhint %}

## Get Help

Not quite sure about something? No problem, I'm happy to help!

Please get in touch via the correct method below for your issue, and I'll be there to help ASAP!

* For bug reports & feature requests: [GitHub Issues](https://github.com/JaffaKetchup/flutter_map_tile_caching/issues)
* For implementation/general support: The *#plugin* channel on the [flutter\_map Discord server](https://github.com/fleaflet/flutter_map#discord-server)
* For other inquiries and licensing: <fmtc@jaffaketchup.dev>

[^1]: Keep your users' tiles organized, and even let them control what goes where!

[^2]: Tiles can belong to multiple stores, and tiles from different sources (template URLs) can be stored in a single store!

[^3]: Use multiple stores at once

[^4]: Download rectangles, circles, line-based, any other freehand polygon, and any combination of those!

[^5]: Avoid downloading redundant, waste-of-space tiles that cover oceans, with this unique functionality, and bless your users with the gift of more usable capacity for useful maps!

[^6]: Downloading from a server with a rate limit? No problem: just enable rate limiting and we'll do our best to stick to it!

[^7]: Speed is very dependent on tile server ability, network delays, and device processing power. Actual speed is likely to be considerably lower.

    1500 tiles was tested from the included testing tile server running on localhost, on a Windows 11 (Intel 12th Gen i7-12700H CPU & DDR5 4800MHz RAM) with 10 downloading threads & a buffer of 500 tiles.

[^8]: Disclaimer: some of these projects/businesses use FMTC licensed under a paid [alternative proprietary license](#proprietary-licensing), whilst others use an alternative license for free as they are open-source or non-revenue.

[^9]: Those compatible with flutter\_map. Some tile server's may forbid bulk downloading.

[^10]: Some tile servers may forbid this activity. Check your tile server's ToS.


# Is FMTC Right For Me?

*In a one word answer: Yes.*

FMTC aims to provide all the functionality you will need for advanced offline mapping in your app, and the unique features that will help set your app apart from the competition, in a way that requires little knowledge of the internals of 'flutter\_map' and other caching fundamentals, and little effort from you (for most setups).

However, this doesn't mean there aren't any other options to consider! The flutter\_map documentation gives a good overview of the different types of caching.

{% embed url="<https://docs.fleaflet.dev/tile-servers/offline-mapping>" %}

In general, there's a few reasons why I wouldn't necessarily recommend using FMTC:

* You're not planning to make use of bulk downloading or import/export functionality
* You don't need the fine grained control of stores (and their many-to-many relationship with tiles that keeps duplication minimal across them)
* You want to ship every user a standardized tileset, and you don't really need other functionality after that point

Although FMTC will still handle all these situations comfortably, other options may be better suited, and/or more lightweight.

**FMTC is an all-in-one solution. It specialises in the functionalities which are difficult and time-consuming to get right (such as bulk downloading), and their integration with the essential function of browse caching, through a clean, unified API, and a clean, decluttered, and fast backend.**

**Other libraries or DIY solutions may not be all-in-one, but they may be all that's required for your app.** Caching alone is not difficult or time consuming to setup yourself, either through a custom `TileProvider` backed by a non-specialised image caching provider such as '[cached\_network\_image](https://pub.dev/packages/cached_network_image)', or the other browse-caching-only flutter\_map plugin '[flutter\_map\_cache](https://pub.dev/packages/flutter_map_cache)' if you need to save even more time at the expense of more external dependencies.

{% hint style="warning" %}
Another consideration for non-GPL-licensed projects is abiding by FMTC's GPL license. This is especially true for proprietary products.

For more information about licensing, please see [(Proprietary) Licensing](/proprietary-licensing).
{% endhint %}

If you're still not sure, please get in touch: [flutter\_map\_tile\_caching](/#get-help)! I'm always happy to offer honest guidance :)


# Supporters

## Supporting Me

Hi there 👋 My name is Luka, but I go by JaffaKetchup!

I'm currently in full-time education in the UK studying Computer Science, Geography, and Mathematics, and have been building my software development skills alongside my education for many years. I specialise in the Flutter and Dart technologies to build cross-platform applications, and have over 4 years of experience both from a hobby and commercial standpoint.

I've worked as a senior developer with a small team at WatchEnterprise to develop their Flutter-based WatchCrunch social media app for Android & iOS.

I'm one of a small team of maintainers for Flutter's №1 non-commercially aimed mapping library 'flutter\_map', for which we make internal contributions, regulate and collaborate with external contributors, and offer support to a large community. I also personally develop a multitude of extension libraries, such as 'flutter\_map\_tile\_caching'. In addition, I regularly contribute to OpenStreetMap, and continously improve my skills with personal experimental projects.

I'm eager to earn other languages, and more than happy to talk about anything software development related!

Sponsorships & donations allow me to further my education, whilst spening even more time developing the open-source projects that you use & love. I'm grateful for any amount you can spare, all support means a lot to me :) If you can't support me financially, please consider leaving a star and a like on projects that worked well for you.

I'm extremely greatful for any amount you can spare!

{% embed url="<https://github.com/sponsors/JaffaKetchup>" %}
Sponsor Me via GitHub Sponsors
{% endembed %}

## Supporters

Many thanks to all my supporters, donations of all sizes mean a lot to me, and encourage and enable me to continue my work on FMTC and other open-source projects.

In no particular order:

* [@tonyshkurenko](https://github.com/tonyshkurenko)
* [@Mmisiek](https://github.com/Mmisiek)
* [@huulbaek](https://github.com/huulbaek)
* [@andrewames](https://github.com/andrewames)
* [@ozzy1873](https://github.com/ozzy1873)
* [@eidolonFIRE](https://github.com/eidolonFIRE)
* [@weishuhn](https://github.com/weishuhn)
* [@mohammedX6](https://github.com/mohammedX6)
* [@quentinchaignaud](https://github.com/quentinchaignaud)
* [@Mayb3Nots](https://github.com/Mayb3Nots)
* [@T-moz](https://github.com/T-moz)
* [@micheljung](https://github.com/micheljung)
* *+ more anonymous or private donors*

And also a thank you to the businesses listed in [flutter\_map\_tile\_caching](/#trusted-by-many), and more, for abiding by the GPL license of this repository.


# (Proprietary) Licensing

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution.
{% endhint %}

*I am not a lawyer, and this information is to the best of my understanding only. You are urged to read the license yourself for a thorough understanding.*

This project is released under GPL v3. For detailed information about this license, see <https://www.gnu.org/licenses/gpl-3.0.en.html>. [choosealicense.com](https://choosealicense.com/licenses/gpl-3.0/) summarises the license with the following paragraph:

> Permissions of this strong copyleft license are conditioned on **making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license**. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights.

Essentially, whilst you can use this code within commercial projects, they must not be proprietary - they incorporate this 'licensed work' so they must be available under the same license. You must distribute your source code (at least on request) (under the same GPL v3 license) to anyone who uses your program.

I learnt (and am still learning) to code with free, open-source software due to my age and lack of money, and for that reason, I believe in promoting open-source wherever possible to give equal opportunities to everybody, no matter their age, financial position, or any other characteristic. I'm not sure it's fair for commercial & proprietary applications to use software made by people for free out of generosity without giving back to the ecosystem or maintainer(s).\
On the other hand, I recognise that commercial businesses may want to use my projects for their own proprietary applications, and are happy to support me, and I am also trying to make a small amount of money from my projects, by donations and by selling licenses!

Therefore, if you would like a license to use this software within a proprietary application, I am willing to sell a (preferably yearly) license. If this seems like what you'd be interested in, please do not hesitate to get in touch at <fmtc@jaffaketchup.dev>. Please include details of your project if you can, and the approximate scale/audience for your app; I try to find something that works for everyone, and I'm happy to negotiate! If you're a non-profit organization, I'm happy to also offer an alternative license for free\*[^1]!

{% hint style="info" %}
Want to see who else uses FMTC? Check out [flutter\_map\_tile\_caching](/#trusted-by-many)!
{% endhint %}

[^1]: Decision will be case dependent. Please get in touch, and I'll be happy to talk!


# Quickstart

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution. For more information, please see [(Proprietary) Licensing](/proprietary-licensing).
{% endhint %}

This page guides you through a simple, fast setup of FMTC that just enables basic browse caching, without any of the cool features that you can discover throughout the rest of this documentation.

## 1. [Install](/get-started/installation)

Depend on the latest version of the package from pub.dev, then import it into the appropriate files of your project.

{% code title="Console/Terminal" %}

```sh
flutter pub add flutter_map_tile_caching
```

{% endcode %}

```dart
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
```

Depending on your platform, some additional setup may be neccessary (particularly on macOS).

## 2. [Initialise](/usage/initialisation)

Perform the startup procedure to allow usage of FMTC's APIs and allow FMTC to spin-up the underlying connections & systems.

Here, we'll use the built-in, default 'backend' storage, which uses [ObjectBox](https://pub.dev/packages/objectbox). We'll perform the initialisation just before the app starts, so we can be sure that it will be ready and accessible throughout the app, at any time.

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">import 'package:flutter/widgets.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

Future&#x3C;void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    
<strong>    await FMTCObjectBoxBackend().initialise(...);
</strong>    
    // ...
    
    runApp(MyApp());
}
</code></pre>

## 3. [Create a store](/usage/root-and-stores/stores)

Create a container that is capable of storing tiles, and can be used to [browse cache](#user-content-fn-1)[^1] and bulk download.

Here, we'll create one called 'mapStore', directly after initialisation. Any number of stores can be created, at any point!

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">    // ...
    
<strong>    await FMTCStore('mapStore').manage.create();
</strong>    
    // ...
</code></pre>

## 4. [Connect to 'flutter\_map'](/usage/integrating-with-a-map)

Add FMTC's specialised `TileProvider` to the `TileLayer`, to enable browse caching, and retrieval of tiles from the specified store.

{% hint style="warning" %}
Double check that the name of the store specified here is the same as the store created above!
{% endhint %}

{% code title="map\_view\.dart" %}

```dart
import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

// Stateful widget class definition

class _...State extends State<...> {
  final _tileProvider = FMTCTileProvider(
    stores: const {'mapStore': BrowseStoreStrategy.readUpdateCreate},
  );
  
  @override
  Widget build(BuildContext context) {
    return FlutterMap(
      options: MapOptions(),
      children: [
        TileLayer(
          urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
          userAgentPackageName: 'com.example.app',
          tileProvider: _tileProvider,
          // Other parameters as normal
        ),
      ],
    );
  }
}
```

{% endcode %}

## 5. Wow! Look at that caching...

{% hint style="success" %}
You should now have a basic working implementation of FMTC that caches tiles for you as you browse the map!

There's a lot more to discover, from store management to bulk downloading, and from statistics to exporting/importing.
{% endhint %}

{% hint style="warning" %}
Before using FMTC, especially to bulk download or import/export, ensure you comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

For example, OpenStreetMap's tile server forbids bulk downloading: <https://operations.osmfoundation.org/policies/tiles>. And Mapbox has restrictions on importing/exporting from outside of the user's own device.

For testing purposes, check out the testing tile server included in the FMTC project: [Testing Tile Server](/usage/bulk-downloading/testing-tile-server).
{% endhint %}

[^1]: This caching occurs automatically as the map is moved by the user, and new tiles load.


# Installation

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution. For more information, please see [(Proprietary) Licensing](/proprietary-licensing).
{% endhint %}

{% hint style="success" %}
Looking to start using FMTC in your project? Check out the [Quickstart](/get-started/quickstart) guide!
{% endhint %}

## Install

{% tabs %}
{% tab title="from pub.dev" %}
For the latest stable release, depend on the package as you normally would by adding it to your pubspec.yaml manually or using:

```sh
flutter pub add flutter_map_tile_caching
```

{% endtab %}

{% tab title="from GitHub directly" %}
To depend on potentially unstable commits from a branch (the commits on main usually represent stable releases), for development or testing, follow [#from-pub.dev](#from-pub.dev "mention"), then add the following lines to your pubspec.yaml file under the `dependencies_override` section:

{% code title="pubspec.yaml" %}

```yaml
dependency_overrides:
    flutter_map_tile_caching:
        git:
            url: https://github.com/JaffaKetchup/flutter_map_tile_caching.git
            # ref: a commit hash, branch name, or tag (otherwise defaults to master)
```

{% endcode %}
{% endtab %}
{% endtabs %}

Then, depending on the platforms you are developing for, you may need to follow ObjectBox's installation instructions for your platform (which can be found originally [here](https://docs.objectbox.io/getting-started), under the Flutter tab):

{% tabs %}
{% tab title="Android" %}
Try building the app - it might just work, especially if you are using other plugins in your app!

<details>

<summary>If it does not build successfully</summary>

If the error message seems to indicate that the "Android NDK" version needs to be higher, follow the instructions.

Usually this involves the following change to your app-level build.gradle(.kts) config:

<pre class="language-diff" data-title="android/app/build.gradle(.kts)"><code class="lang-diff">android {
    namespace = "*"
    compileSdk = flutter.compileSdkVersion
-   ndkVersion = flutter.ndkVersion
<strong>+   ndkVersion = &#x3C;the version specified at the end of the error log>
</strong>
    ...
}
</code></pre>

</details>
{% endtab %}

{% tab title="macOS" %}
macOS apps may need to target macOS 10.15. In your Podfile, change the platform and in the `Runner.xcodeproj/project.pbxproj` file, update `MACOSX_DEPLOYMENT_TARGET`.

***

To enable your app to run in sandboxed mode (which is a requirement for most applications), you'll need to specify an application group. Follow these instructions:

1. Check all `macos/Runner/*.entitlements` files contain a section with the group ID
2. If necessary, change the string value to the `DEVELOPMENT_TEAM` you can find in your Xcode settings, plus an application-specific suffix. Due to macOS restrictions the complete string must be 19 characters or shorter. For example:

   <pre class="language-xml" data-title="macos/Runner/*.entitlements"><code class="lang-xml">&#x3C;dict>
     &#x3C;key>com.apple.security.application-groups&#x3C;/key>
     &#x3C;array>
   <strong>    &#x3C;string>FGDTDLOBXDJ.demo&#x3C;/string>
   </strong>  &#x3C;/array>  
   ...  
   &#x3C;/dict>
   </code></pre>
3. When [initialising FMTC](/usage/initialisation), make sure to pass this string to the `macosApplicationGroup` argument
   {% endtab %}

{% tab title="Web (unsupported)" %}
{% hint style="warning" %}
Although **FMTC will compile on the web**, the default FMTC [backend](/usage/initialisation#backends) does not support the web platform. `FMTCObjectBoxBackend.initialise` and `.uninitialise` will throw `UnsupportedError`s if invoked on the web. Other methods will throw `RootUnavailable` as normal.
{% endhint %}
{% endtab %}
{% endtabs %}

## Import

After installing the package, import it into the necessary files in your project:

```dart
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
```


# Example Application

This package contains a full example application - prebuilt for Android and Windows by GitHub Actions - showcasing the most important features of this package and its modules.

{% hint style="info" %}
The example app isn't intended for beginners or as a starting point for a project. It is intended for evaluation purposes, to discover FMTC's capabilities, and how it might be implemented into an app.

To start using FMTC in your own app, please check out the [Quickstart](/get-started/quickstart) guide instead.
{% endhint %}

{% hint style="success" %}
The example application pairs perfectly with the testing tile server included in the FMTC project: [Testing Tile Server](/usage/bulk-downloading/testing-tile-server)!
{% endhint %}

## Prebuilt Artifacts

If you can't build from source for your platform, our GitHub Actions CI system compiles the example app to artifacts for Windows and Android, which just require unzipping and installing the .exe or .apk found inside.

{% hint style="info" %}
Note that these artifacts are built automatically from the ['master' branch](https://github.com/fleaflet/flutter_map), so may not reflect the the latest release on pub.dev.
{% endhint %}

{% embed url="<https://nightly.link/JaffaKetchup/flutter_map_tile_caching/workflows/main/main>" %}
Latest Build Artifacts (thanks [nightly](https://nightly.link/))
{% endembed %}

## Build From Source

If you need to use the example app on another platform, you can build from source, using the 'example' directory of the repository.


# v9 -> v10 Migration

{% hint style="success" %}
v10 focuses on completing the 'tiles-across-stores' functionality from v9, by bringing it to browse caching, which huge amounts of customizability and flexibility.

Check out the CHANGELOG: <https://pub.dev/packages/flutter_map_tile_caching/changelog>! This page only covers breaking changes, not feature additions and fixes.

Please consider donating: [flutter\_map\_tile\_caching](/#supporting-me)! Any amount is hugely appreciated!
{% endhint %}

## Browse Caching

We recommend following the steps on [Integrating With A Map](/usage/integrating-with-a-map) from start to finish to migrate your existing `FMTCTileProvider` to v10, as the API has significant changes, and the steps include new guidance to ensure best practice and performance.

Some changes are highlighted below:

<details>

<summary>Absorbed <code>FMTCTileProviderSettings</code> directly into <code>FMTCTileProvider</code></summary>

The properties within have become properties directly in `FMTCTileProvider`. This also means the automatic global system (where the settings could be set once then used everywhere) has also been removed.

The simplifies the code internals and removes an unnecessary layer of abstraction.

</details>

<details>

<summary>Replaced <code>maxStoreLength</code> with property directly on stores</summary>

It has been replaced with a property on each store itself. It can be set at creation, or changed after, and read at any time:

```dart
await FMTCStore('storeName').manage.create(maxLength: 1000);
await FMTCStore('storeName').manage.setMaxLength(null); // Disable max length
final maxLength = await FMTCStore('storeName').manage.maxLength;
```

This is more suitable for providers now that more than one store may be used, potentially each with a different maximum length.

</details>

<details>

<summary>Replaced <code>obscuredQueryParams</code> with <code>urlTransformer</code></summary>

It has been replaced on the `FMTCTileProvider` with a more flexible custom callback which may perform any processing logic required, and a utility method if the old behaviour is still desired.

To migrate directly, see the example setup: [Integrating With A Map](/usage/integrating-with-a-map#two-static-named-stores-with-a-url-transformer).

</details>

<details>

<summary>Renamed <code>CacheBehavior</code> with <code>BrowseLoadingStrategy</code></summary>

This has been renamed to fit better with a newly introduced enumerable that work together to configure the tile provider's logic.

(And also, no more US/UK confusion :D)

</details>

## Bulk Downloading

Most of bulk downloading hasn't had any breaking changes, with the major exception of these:

<details>

<summary><code>startForeground</code> now returns two streams</summary>

It now returns one stream of `DownloadProgress`s, and one of the new `TileEvent`s. This means that checks no longer have to be made to ensure a `TileEvent` is not a repeated event (except where using the new feature to retry failed tiles, discussed below), and also means they can be more easily listened to independently.

To migrate, listen to necessary streams seperately.&#x20;

</details>

<details>

<summary>Renovated <code>TileEvent</code> completely</summary>

Properties in v9 were nullable dependent on whether they were available, and this could be checked with `.result` (`TileEventResult`).

`TileEvent` has been split into a tree of classes, which are sealed. This means that the available properties are fully safe and no null-checks need to be made. Switch-case statements and normal `is` checks can be used, which statically changes the type of the `TileEvent` to a subtype appropriately. Each subtype represents a specific outcome of the tile download, and mixes in certain types.

* `SuccessfulTileEvent` is emitted when a tile is successfully downloaded\
  *Root subtype, mixes in `TileEventFetchResponse` (makes the raw fetch response from the server available) and `TileEventImage` (makes tile image available)*
* `SkippedTileEvent` (\*[^1])\
  *Root subtype, mixes in `TileEventImage`*
  * `ExistingTileEvent` is emitted when a tile is skipped because it already exists
  * `SeaTileEvent` is emitted when a tile is skipped because it was a sea tile\
    *Also mixes in `TileEventFetchResponse`*
* `FailedTileEvent` (\*[^1])\
  *Root subtype*
  * `NegativeResponseTileEvent` is emitted when a tile fails because the server did not respond with 200 OK\
    *Mixes in `TileEventFetchResponse`*
  * `FailedRequestTileEvent` is emitted when a tile fails because the request to the server was not made successfully (eligible for retry)

</details>

<details>

<summary>Renamed properties within <code>DownloadProgress</code></summary>

Most are renamed obviously to improve clarity. Some may have had the exact included figures changed.

</details>

[^1]: another abstract type


# Initialisation

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution. For more information, please see [(Proprietary) Licensing](/proprietary-licensing).
{% endhint %}

FMTC relies on a self-contained 'environment', called a [#backends](#backends "mention"), that requires initialisation (and configuration) before it can be used. This allows the backend to start any necessary seperate threads/isolates, load any prerequisites, and open and maintain a connection to a database. This environment/backend is then accessible internally through a(\*[^1]) singleton, so initialisation is not required again.

## Initialisation

Initialisation should be performed before any other FMTC or backend methods are used, and so it is usually placed just before `runApp`, in the `main` method. This shouldn't have any significant effect on application startup time.

{% hint style="warning" %}
If initialising in the `main` method before `runApp` is called, ensure you also call `WidgetsFlutterBinding.ensureInitialised()` prior to the backend initialisation.
{% endhint %}

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">import 'package:flutter/widgets.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

Future&#x3C;void> main() async {
    WidgetsFlutterBinding.ensureInitialized();   
    
    try {
<strong>        await FMTCObjectBoxBackend().initialise(...); // The default/built-in backend
</strong>    } catch (error, stackTrace) {
        // See below for error/exception handling
    }
    
    // ...
    
    runApp(MyApp());
}
</code></pre>

{% hint style="danger" %}
Do not call any other FMTC methods before initialisation. Doing so will cause a `RootUnavailable` error to be thrown.
{% endhint %}

{% hint style="danger" %}
Do not attempt to initialise the same backend multiple times, or initialise multiple backends simultaenously. Doing so will cause a `RootAlreadyInitialised` error to be thrown.
{% endhint %}

{% hint style="warning" %}
Avoid using FMTC in a seperate thread/`Isolate`. FMTC backends already make extensive use of multi-threading to improve performance.

If it is essential to use FMTC in a seperate thread, ensure that the initialisation is called in the thread where it is used. Be cautious of using FMTC manually across multiple threads simultaneously, as backends may not properly support this, and unexpected behaviours may occur.
{% endhint %}

### Error Handling

One particular place where exceptions can occur more frequently is during initialisation. The code sample above includes a `try`/`catch` block to catch these errors. If an exception occurs at this point, it's likely unrecoverable (for example, it might indicate that the underlying database has been corrupted), and the best course of action is often to manually delete the FMTC root directory from the filesystem.

The default directory can be found and deleted with the following snippet (which requires 'package:path' and 'package:path\_provider':

```dart
import 'dart:io';

import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';

final dir = Directory(
  path.join(
    (await getApplicationDocumentsDirectory()).absolute.path,
    'fmtc',
  ),
);

await dir.delete(recursive: true);

// Then reinitialise FMTC
```

## Uninitialisation

It is also possible to un-initialise FMTC and the current backend. This should be rarely required, but can be performed through the `uninitialise` method of the backend if required. Initialisation is possible after manual uninitialisation.

## Backends

FMTC supports attachment of any custom storage mechanism, through an `FMTCBackend`. This allows users to pick their favourite database engine, or conduct in-memory testing.

{% hint style="success" %}
Only one backend is built-into FMTC: the `FMTCObjectBoxBackend`. This backend uses the [ObjectBox library](https://pub.dev/packages/objectbox) to store data.
{% endhint %}

{% hint style="warning" %}
ObjectBox has a complex license model - the build time dependency is open-source, whilst the native library *runtime only* dependency is under a closed-source (but relatively relaxed) [license](https://objectbox.io/0209-ob-binary-license/) (that is liable to change at ObjectBox's will).

This is not an issue for the majority of applications. However, ObjectBox is known to be (rightly or wrongly) banned as a dependency from apps on F-Droid (last checked September 2024).

Future updates to FMTC will implement alternative backends using other libraries, and the default/preferred backend may indeed change in future.

For more information, please see: <https://github.com/JaffaKetchup/flutter_map_tile_caching/issues/167>.
{% endhint %}

[^1]: Internally, more than one singleton may be used in a backend, and to access a backend. However, this is beyond the scope of this page.


# Root & Stores

FMTC uses a *root* and *stores* to structure its data. In general, a single root exists (which uses a single [backend](/usage/initialisation#backends)), which contains multiple named stores. Cached tiles can belong to multiple stores, which reduces duplication and maximizes flexibility.

{% hint style="info" %}
The structures use the ambient backend when a method is invoked on it, not at construction time.

Therefore, it is possible to construct an `FMTCStore`/`FMTCRoot` before initialisation, but 'using' any methods on it will throw `RootUnavailable`.
{% endhint %}


# Root

A root contains statistics about itself and the stores, as well as information for the bulk download [Recovery](/usage/bulk-downloading/recovery) system, and access to the import/export functionality.

Roots are unnamed, and the current root is accessed through `FMTCRoot`:

<pre class="language-dart"><code class="lang-dart"><strong>// final root = FMTCRoot;
</strong>final databaseSize = await FMTCRoot.stats.realSize;
</code></pre>

{% hint style="info" %}
To manage the root, use the methods on the backend.
{% endhint %}

## Statistics

`FMTCRoot.stats` allows access to statistics, as well as listing of all existing stores, and the watching of changes in multiple/all stores.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootStats-class.html>" %}

{% hint style="info" %}
Remember that the `size` and `length` statistics in the root may not be equal to the sum of the same statistics of all available stores, because tiles may belong to many stores, and these statistics do not count any tile multiple times.
{% endhint %}

## Recovery

{% content-ref url="/pages/QjStFkRCKXgLWD1lY19Q" %}
[Recovery](/usage/bulk-downloading/recovery)
{% endcontent-ref %}

## Import/Export

{% content-ref url="/pages/DHmN1uIezWbyPpV1SMzB" %}
[Import/Export](/usage/import-export)
{% endcontent-ref %}


# Stores

Stores maintain references to all tiles which belong to it, and also contain customizable metadata and cached statistics.

They are referenced by name, the single argument of `FMTCStore`.

{% hint style="warning" %}
Ensure names of stores are consistent across every access. "Typed"/code-generated stores are not provided, to maintain flexibility.
{% endhint %}

{% hint style="warning" %}
Construction of an `FMTCStore` object does create the underlying store, as this is an asynchronous task. It must be created before it may be used.
{% endhint %}

<pre class="language-dart"><code class="lang-dart"><strong>// final store = FMTCStore('storeName');
</strong>await FMTCStore('storeName').manage.create(); // Creates the store
</code></pre>

## Management

`FMTCStore().manage` allows control over the store and its contents.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreManagement-class.html>" %}

## Statistics

`FMTCStore().stats` allows access to:

* statistics
* retrieval of a recent tile (as an image)
* watching of changes to the store

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreStats-class.html>" %}

## Metadata

`FMTCStore().metadata` allows access and control over a simple persistent storage mechanism, designed for use with custom data/properties/fields tied to the store. For example, in some apps, it could store the `BrowseStoreStrategy` or URL template/source.

Data is interpreted in key-value pair form, where both the key and value are `String`s. Internally, the default backend stores it as a flat JSON structure. The metadata is stored directly on the store: if the store is deleted, it is deleted, and an exported store retains its metadata. More advanced requirements will require use of a separate persistence mechanism.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreMetadata-class.html>" %}

{% hint style="info" %}
Remember that `metadata` does not have any effect on internal logic: it is simply an auxiliary method of storing any data that might need to be kept alongside a store.
{% endhint %}


# Integrating With A Map

"Browse caching" occurs as the map loads tiles as the user interacts with it (or it is controlled by a `MapController`).

To inject the browse caching logic into flutter\_map's tile loading process, FMTC provides a custom `TileProvider`: `FMTCTileProvider`.

Setup is quick and easy in many cases, but this guides through every step in the order in which it should be done to ensure best performance and all factors have been considered.

{% hint style="info" %}
Remember that a store can hold tiles from more than one server/template URL.
{% endhint %}

## Walkthrough

{% hint style="success" %}
Before you can get started, make sure you've [initialised FMTC](/usage/initialisation) & created one or more [Stores](/usage/root-and-stores/stores)!
{% endhint %}

{% stepper %}
{% step %}

### Choose where to construct the tile provider

Where & how you choose to construct the `FMTCTileProvider` object has a major impact on performance and tile loading speeds, so it's important to get it right.

Minimize reconstructions of this provider by constructing it outside of the `build` method of a widget wherever possible. Because it is not a `const`ant constructor, and it will be in a non-`const`ant context (`TileLayer`), every rebuild will trigger a potentially expensive reconstruction.

However, in many cases, such as where one or more properties (as described in following stages) depends on inherited data (ie. via an `InheritedWidget`, `Provider`, etc.), this is not possible.\
In this case, read the tip in the [API documentation](https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/FMTCTileProvider-class.html) carefully. In summary, you should construct as many arguments as possible outside of the `build` method, particularly a HTTP `Client` and any objects or callbacks which do not have a useful equality/hash code themselves.
{% endstep %}

{% step %}

### Choose *which* stores it will interact with

The tile provider can interact in multiple ways with multiple stores at once, affording maximum flexibility. Defining *how* it interacts with these stores will be done in following stages, but you first need to define which stores it will interact with.

How exactly you need to define the stores depends on how much flexibility you need:

{% tabs %}
{% tab title="Specified stores with specified strategies" %}
It is more common you will want to interact with just one or a defined set of stores at any one time. In this case, use the default constructor. You'll need to choose *how* (what strategy) it interacts with each store in following stages.

The parameters you will need to use will depend on how advanced your use-case is, but it will progress in a linear fashion:

1. The mandatory `stores` argument takes a mapping of store names to the strategy to be used for that store.
2. **If** you want to apply another strategy to all *other* available stores (whose names are not in the `stores` mapping), use the `otherStoresStrategy` argument.
3. **If** you define *that* strategy, but you still want to disable interaction with some stores altogether, add these stores to the `stores` mapping with associated `null` values. (If `otherStoresStrategy` is not defined, stores mapped to `null` have no difference to if they were not included in the mapping at all.)

{% hint style="success" %}
Ensure that all specified stores exist.
{% endhint %}
{% endtab %}

{% tab title="All stores with one strategy" %}
If you want it to interact with all available stores (those which have been created), all in the same way (see following stages), use the `allStores` named constructor, and that's it! FMTC will efficiently apply the strategy you choose in the next stage across all stores without you needing to track it yourself.

Remember that the provider can also read tiles from multiple stores, so this may not be necessary - but the option is there!
{% endtab %}
{% endtabs %}
{% endstep %}

{% step %}

### Choose *how* it will interact with the stores

The `BrowseStoreStrategy`s tell FMTC how it should read, update, and create tiles in the store it is associated to.

In the `allStores` constructor, it is passed to `allStoresStrategy` and applied to all available stores (as described in the previous stage).

Otherwise, in the default constructor, one strategy is assigned to each store, plus optionally one to all other available stores (as described in the previous stage).

There are three possible strategies:

* `.read`: only read tiles from the associated store
* `.readUpdate`: read tiles, and also update existing tiles in the associated store, if necessary
* `.readUpdateCreate`: read, update (if necessary), and create tiles in the associated store
  {% endstep %}

{% step %}

### Choose the preferred & fallback source for tiles

The `BrowseLoadingStrategy`s (previously known as `CacheBehavior`s) tell FMTC the preferred source for tiles to be loaded from, and how to fallback if that source fails. It is passed to the `loadingStrategy` parameter.

There are three possible priorities:

| Strategy                 | Preferred method | Fallback method |
| ------------------------ | ---------------- | --------------- |
| `.cacheOnly`             | Cache            | *Failure*       |
| `.cacheFirst` \*[^1]     | Cache            | Network (URL)   |
| `.onlineFirst`           | Network (URL)    | Cache           |
| *Standard tile provider* | *Network (URL)*  | *Failure*       |

The `cacheOnly` strategy essentially disables writing to the cache, and makes the chosen `BrowseStoreStrategy`s above `.read` redundant.

{% hint style="warning" %}
The `onlineFirst` strategy may make tile loading appear slower when not connected to the Internet/network.

This is because the HTTP client may attempt to make the request anyway (Dart does not realise sometimes that the Internet is not available), in which case, the HTTP timeout set in the client must elapse before the tile is retrieved from the cache.
{% endhint %}

<details>

<summary>Customizing the interaction with <code>otherStoresStrategy</code> (if set)</summary>

The `useOtherStoresAsFallbackOnly` parameter concerns the behaviour of FMTC when a tile does not belong to any stores set in the `stores` mapping, but does belong to stores covered by `otherStoresStrategy`.

* If `false` (as default), then the tile will be used without attempting the fallback method.
* If `true`, then the tile will only be used if the fallback method fails.

This is not of concern if the strategy is `onlineFirst`, as if the always-attempted network fetch fails, the tile will always be used from the unspecified store.

</details>

Also see how this strategy influences tile updates in stage 6.
{% endstep %}

{% step %}

### Ensure tiles are resilient to URL changes

To reference (enable correct creation/updating/reading of) tiles, FMTC uses a 'storage-suitable UID' derived from the tile's URL.\
Any one tile from the same server (style, etc. allowing) should have one storage-suitable UID which does not change.

On some servers, it may be acceptable for the UID to be the same as the tile URL. For example, the OpenStreetMap tile server URL for the tile at 0/0/0 will always be `https://tile.openstreetmap.org/0/0/0.png`.\
However, on some servers, the URL may change, but still point to the same desired tile. Consider the following URL: `https://tile.paid.server/0/0/0.png?volatile_key=123`. In this case, the URL requires an API key to retrieve the tile. If the UID was the same as the URL, but the key changes - for example, because it was leaked and refreshed - then FMTC would be unable to reference this tile when it encounters the same URL  with the different key. This would mean the tile could not be read or updated, which may significantly impact your app's functionality.

To fix this, the `urlTransformer` parameter takes a callback which gets passed the tile's real URL, and should return a stable storage-suitable UID. For example, it should remove the offending query parameters.

{% hint style="warning" %}
The `urlTransformer` defined here should usually be the same as the transformer defined for a bulk download. Otherwise, tiles which have been bulk downloaded may not be able to be referenced, for example if an API key changes.

If the `TileLayer` used to start the bulk download uses an `FMTCTileProvider` with a defined `urlTransformer` as the tile provider, it will be used automatically, otherwise the bulk download also takes the `urlTransformer` directly.
{% endhint %}

If the offending part of the URL occurs as in the example above - as part of a query string - FMTC provides a utility callback which can be used as the transformer to remove the offending key & value cleanly.\
`FMTCTileProvider.urlTransformerOmitKeyValues` takes the tile URL as input, as well as a list of keys. It will remove both the key and associated value for each listed key.\
It may also be customized to use a different 'link' ('=') and 'delimiter' ('&') character, and it will remove any `key<link>value` found in the URL, not just from after the '?' character.
{% endstep %}

{% step %}

### Configure tile updates

A tile will be updated in a store if all the following conditions are met:

* [x] The tile already exists in the store
* [x] The `BrowseStoreStrategy` is `.readUpdate` or `.readUpdateCreate`
* [x] The `BrowseLoadingStrategy` is not `.cacheOnly`
* [x] The tile can be fetched successfully from the network/Internet
* [x] *...and if either (or both)...*
  * The `BrowseLoadingStrategy` is `.onlineFirst`
  * **The tile has been flagged for updating**

The `cachedValidDuration` parameter can be used to set an expiry for all tiles written whilst it is set. Once a tile is expired, it will be flagged as needing updating. By default, there is no expiry set.
{% endstep %}

{% step %}

### Configure other parameters

<details>

<summary>Basic hits &#x26; misses statistics (<code>recordHitsAndMisses</code>)</summary>

By default, every tile attempted during browsing records either a hit or miss.

A hit is recorded when a tile is read from the cache without attempting the network in all stores in which the tile exists & were present in the `stores` mapping (and not explicitly set `null`), or in which the tile exists if `otherStoresStrategy` was set.

In every other case, a miss is recorded in all stores present in the `stores` mapping  (and not explicitly set `null`), or in all stores if `otherStoresStrategy` was set.

This information may not be useful or used in many apps, and so it may be disabled by setting it `false`. This will also improve performance (reduce tile loading times and device memory/storage operations). Additionally, more detailed and advanced metrics may be obtained by setting up a `tileLoadingInterceptor` (as below).

</details>

<details>

<summary>Handle tile load failures (<code>errorHandler</code>)</summary>

*This feature is completely standalone to the `TileLayer`'s `errorImage`.*

By default, when a tile cannot be loaded, an `FMTCBrowsingError` is thrown containing some information about why the load failed. This is because *something* must be returned or thrown by internal `ImageProvider`.

However, it is possible to provide an error handler, which gets passed the failure as an argument, and may optionally return bytes (which must be decodable by Flutter). If it does return bytes, the error will not be thrown, and instead the bytes will be displayed in place of the tile image.

</details>

<details>

<summary>Intercept tile load events &#x26; info (<code>tileLoadingInterceptor</code>)</summary>

To track (eg. for debugging and logging) the internal tile loading mechanisms, an interceptor may be used.&#x20;

For example, this could be used to debug why tiles aren't loading as expected (perhaps in combination with `TileLayer.tileBuilder` & `ValueListenableBuilder` as in the example app), or to perform more advanced monitoring and logging than the hit & miss statistics provide.

The interceptor consists of a `ValueNotifier`, which allows FMTC internals to notify & push updates of tile loads, and allows the owner to listen for changes as well as retrieve the latest update (`value`) immediately.\
The object within the `ValueNotifier` is a mapping of `TileCoordinates` to [`TileLoadingInterceptorResult`](https://pub.dev/documentation/flutter_map_tile_caching/10.0.0-dev.7/flutter_map_tile_caching/TileLoadingInterceptorResult-class.html)s.

</details>

{% hint style="info" %}
Stores may also have a `maxLength` defined (the maximum number of tiles that store may hold). This is enforced automatically during browse caching.
{% endhint %}

{% endstep %}

{% step %}
{% hint style="success" %}
And that's it! FMTC will handle everything else behind the scenes.

If you bulk download tiles, they'll be able to be used automatically as well.
{% endhint %}
{% endstep %}
{% endstepper %}

## Examples

### A single store in a simple static configuration

This is the most simple case where one store exists, using the default constructor and no other parameters except a `BrowseLoadingStrategy`.

```dart
class _...State extends State<...> {
  final _tileProvider = FMTCTileProvider(
    stores: const {'mapStore': BrowseStoreStrategy.readUpdateCreate},
    loadingStrategy: BrowseLoadingStrategy.onlineFirst,
  );
  // and if "mapStore" is the only store, this could also be written as
  final _tileProvider = FMTCTileProvider.allStores(
    allStoresStrategy: BrowseStoreStrategy.readUpdateCreate,
    loadingStrategy: BrowseLoadingStrategy.onlineFirst,
  );
  
  @override
  Widget build(BuildContext context) {
    return FlutterMap(
      options: MapOptions(),
      children: [
        TileLayer(
          urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
          userAgentPackageName: 'com.example.app',
          tileProvider: _tileProvider,
        ),
      ],
    );
  }
}
```

### Two static named stores with a URL transformer

In this case, there are two stores which never change, which use different `BrowseStoreStrategy`s. There is also a `urlTransformer` defined, using the utility method.

```dart
class _...State extends State<...> {
  final _tileProvider = FMTCTileProvider(
    stores: const {
      'store 1': BrowseStoreStrategy.readUpdateCreate,
      'store 2': BrowseStoreStrategy.read,
    },
    urlTransformer: (url) => FMTCTileProvider.urlTransformerOmitKeyValues(
      url: url,
      keys: ['access_key'],
    ),
  );
  
  @override
  Widget build(BuildContext context) {
    return FlutterMap(
      options: MapOptions(),
      children: [
        TileLayer(
          urlTemplate: 'https://tile.paid.server/{z}/{x}/{y}.png?access_key={access_key}',
          userAgentPackageName: 'com.example.app',
          additionalOptions: const {
            'access_key': '123',
          },
          tileProvider: _tileProvider,
        ),
      ],
    );
  }
}
```

### Stores set from a Provider/Selector with a URL transformer

{% hint style="success" %}
Note that the URL transformer callback and HTTP client have been defined outside of the `FMTCTileProvider` constructor (which must lie within the `build` method because it depends on inherited data).

Defining the URL transformer this way instead of an anonymous function ensures that the caching key works correctly, which improves the speed of tile loading.

Defining the HTTP client (although it is technically optional) ensures it remains open even when the provider is being repeatedly reconstructed, which means it does not have to keep re-creating connections to the tile server, improving tile loading speed. Note that it is not closed when the widget is destroyed: this prevents errors when the widget is destroyed whilst tiles are still being loaded, and there is very little potential for memory or performance leaks.
{% endhint %}

```dart
class _...State extends State<...> {
  late final _httpClient = IOClient(HttpClient()..userAgent = null);
  String _urlTransformer(String url) =>
      FMTCTileProvider.urlTransformerOmitKeyValues(
        url: url,
        keys: ['access_key'],
      );
  
  @override
  Widget build(BuildContext context) {
    return FlutterMap(
      options: MapOptions(),
      children: [
        Selector<GeneralProvider, Map<String, BrowseStoreStrategy?>>(
          selector: (context, provider) => provider.stores,
          builder: (context, stores, _) => 
            TileLayer(
              urlTemplate: 'https://tile.paid.server/{z}/{x}/{y}.png?access_key={access_key}',
              userAgentPackageName: 'com.example.app',
              additionalOptions: const {
                'access_key': '123',
              },
              tileProvider: FMTCTileProvider(
                stores: stores,
                urlTransformer: _urlTransformer,
                httpClient: _httpClient,
              ),
            ),
      ],
    );
  }
}
```

### Using multiple stores alongside `otherStoresStrategy`, and explicitly disabling a store

```dart
class _...State extends State<...> {
  final _tileProvider = FMTCTileProvider(
    stores: const {
      'store 1': BrowseStoreStrategy.readUpdateCreate,
      'store 2': BrowseStoreStrategy.read,
      // 'store 3' implicitly gets `.readUpdate`,
      'store 4': null, // disabled
    },
    otherStoresStrategy: BrowseStoreStrategy.readUpdate,
  );
  
  @override
  Widget build(BuildContext context) {
    return FlutterMap(
      options: MapOptions(),
      children: [
        TileLayer(
          urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
          userAgentPackageName: 'com.example.app',
          tileProvider: _tileProvider,
        ),
      ],
    );
  }
}
```

[^1]: default


# Bulk Downloading

FMTC provides the ability to bulk download areas of maps in one-shot, known as 'regions'. There are multiple different types/shapes of regions available.

{% hint style="warning" %}
Before using FMTC, especially to bulk download or import/export, ensure you comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

For example, OpenStreetMap's tile server forbids bulk downloading: <https://operations.osmfoundation.org/policies/tiles>. And Mapbox has restrictions on importing/exporting from outside of the user's own device.

For testing purposes, check out the testing tile server included in the FMTC project: [Testing Tile Server](/usage/bulk-downloading/testing-tile-server).
{% endhint %}

Downloading is extremely efficient and fast, and uses multiple threads and isolates to achieve write speeds of hundreds of tiles per second (if the network/server speed allows). After downloading, no extra setup is needed to use them in a map (other than the usual [Integrating With A Map](/usage/integrating-with-a-map)).

## Walkthrough

{% hint style="success" %}
Before you can get started, make sure you've [initialised FMTC](/usage/initialisation) & created one or more [Stores](/usage/root-and-stores/stores)!
{% endhint %}

{% stepper %}
{% step %}

### Define a region

A region represents a geographical area only, not any of the other information required to start a download.

All types of region inherit from `BaseRegion`.

{% tabs %}
{% tab title="Rectangle" %}
`RectangleRegion`s are defined by a `LatLngBounds`: two opposite `LatLng`s.

```dart
final region = RectangleRegion(
    LatLngBounds(LatLng(0, 0), LatLng(1, 1)),
);
```

{% endtab %}

{% tab title="Circle" %}
`CircleRegion`s are defined by a center `LatLng` and radius *in kilometers*.

```dart
final region = CircleRegion(
    LatLng(0, 0), // Center coordinate
    1, // Radius in kilometers
);
```

If you instead have two coordinates, one in the center, and one on the edge, you can use ['latlong2's `Distance.distance()`](https://pub.dev/documentation/latlong2/latest/latlong2/Distance/distance.html) method, as below:

```dart
final centerCoordinate = LatLng(0, 0); // Center coordinate
final region = CircleRegion(
    centerCoordinate,
    const Distance(roundResult: false).distance(
        centerCoordinate,
        LatLng(1, 1), // Edge coordinate
    ) / 1000; // Convert to kilometers
);
```

{% endtab %}

{% tab title="(Poly)Line" %}
`LineRegion`s are defined by a list of `LatLng`s, and a radius in meters.

This could be used to download tiles along a planned travel route, for example hiking or long-distance driving. Import coordinates from a routing engine, or from a GPX/KML file for maximum integration!

```dart
final region = LineRegion(
    [LatLng(0, 0), LatLng(1, 1), ...], // List of coordinates
    1000, // Radius in meters
);
```

{% hint style="warning" %}
This region may generate more tiles than strictly necessary to cover the specified region. This is due to an internal limitation with the region generation algorithm, which uses (rotated) rectangles to approximate the actual desired shape.
{% endhint %}

{% hint style="warning" %}
This type of region may consume more memory/RAM when generating tiles than other region types.
{% endhint %}
{% endtab %}

{% tab title="Custom Polygon" %}
`CustomPolygonRegion`s are defined by a list of `LatLng`s defining the outline of a [simple polygon](https://en.wikipedia.org/wiki/Simple_polygon).

```dart
final region = CustomPolygonRegion(
    [LatLng(0, 0), LatLng(1, 1), ...], // List of coordinates
);
```

{% hint style="warning" %}
Polygons should not contain self-intersections. These may produce unexpected results.

Holes are not supported, however multiple `CustomPolygonRegion`s may be downloaded at once using a `MultiRegion`.
{% endhint %}
{% endtab %}

{% tab title="Multi" %}
`MultiRegion`s are defined by a list of multiple `BaseRegion`s (which may contain more nested `MultiRegion`s).

When downloading, each sub-region specified is downloaded consecutively (to ensure that any `start` & `end` tile range defined is respected consistently.

{% hint style="warning" %}
Regions which overlap will still have the overlapping tiles downloaded for each region.

Multi region's advantage is that it reduces the number of costly setup and teardown operations. It also means that statistic measuring applies over all sub-regions, so it does not need to be managed indepedently.
{% endhint %}
{% endtab %}
{% endtabs %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/BaseRegion-class.html>" %}

It is also possible to reconstruct the region from a `RecoveredRegion`: [Recovery](/usage/bulk-downloading/recovery#recoveredregion).
{% endstep %}

{% step %}

### Add information to make the region downloadable

`BaseRegion`s must be converted to `DownloadableRegion`s before they can be used to download tiles.

These contain the original `BaseRegion`, but also some other information necessary for downloading, such as zoom levels and URL templates.

```dart
final downloadableRegion = region.toDownloadable(
    minZoom: 1,
    maxZoom: 18,
    options: TileLayer(
        urlTemplate: '<your tile server>',
        userAgentPackageName: 'com.example.app',
    ),
),
```

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/BaseRegion/toDownloadable.html>" %}

{% hint style="success" %}
The `TileLayer` passed to the `options` parameter must include both a `urlTemplate` (or WMS configuration) and a `userAgentPackageName`, unless it is only being used to `check` the number of tiles in the region.
{% endhint %}
{% endstep %}

{% step %}

### (Optional) Count the number of tiles in the region

Before continuing to downloading the region, use `countTiles()` to count the number of tiles it will attempt to download. This is accessible through `FMTCStore().download`.

The method takes the `DownloadableRegion` generated above, and will return an `int` number of tiles. For larger regions, this may take a few seconds.

{% hint style="warning" %}
This figure will not take into account any skipped sea tiles or skipped existing tiles, as those are handled at the time of download.
{% endhint %}
{% endstep %}

{% step %}

### Configure and start the download

To start the download, use the `startForeground` method on the existing store you wish to download to:

<pre class="language-dart"><code class="lang-dart">final (:downloadProgress, :tileEvents) =
<strong>  const FMTCStore('mapStore').download.startForeground(
</strong>    ...
  );
</code></pre>

There are many options available to customize the download, which are described fully in the API reference:

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreDownload/startForeground.html>" %}

The download starts as soon as the method is called; it does not wait for listeners.
{% endstep %}

{% step %}

### Monitor the download outputs

Listening to the output streams of the download is something most apps will want to do, to display information to the user (unless operating in a headless mode or in the background).

There are two output streams returned as a record.\
One stream emits events that contain information about the download as a whole, whilst the other stream independently emits an event after the fetch/download of each tile in the region is attempted. See the API documentation for information on the exact emission frequencies of each stream.\
These are returned separately as the first stream emits events more frequently than the second, and this prevents tile events from needed to be repeated\*.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/DownloadProgress-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/TileEvent-class.html>" %}

{% hint style="warning" %}
An emitted `TileEvent` may refer to a tile for which an event has been emitted previously.

See the API documentation for more information.
{% endhint %}
{% endstep %}

{% step %}

### (Optional) Control the download

Listening, pausing, resuming, or cancelling subscriptions to the output streams will not start, pause, resume, or cancel the download. It will only change whether the download emits updates.

Instead, there are methods available to control the download itself.

#### Pause/Resume

If your user needs to temporarily pause the download, with the ability to resume it at some point later (within the same app session), use `pause` and `resume`.

Pausing does not interrupt any tiles that are being downloaded when `pause` is invoked. Instead, the download will pause after the tile has been downloaded. `pause`'s returned `Future` completes when the download has actually paused (or after `resume` is called whilst still pausing).

Pausing also does not cause the buffer to be flushed (if buffering is in use).

If listening to the `DownloadProgress` stream, an event will be emitted when pausing and resuming.

Use `isPaused` to check whether the download is currently paused.

#### Cancel

If your user needs to stop the download entirely, use `cancel`.

Cancelling does not interrupt any tiles that are being downloaded when `cancel` is invoked. The returned future completes when the download has stopped and been cleaned-up.

Any buffered tiles are written to the store before the future returned by `cancel` is completed.

It is safe to use `cancel` after `pause` without `resume`ing first.
{% endstep %}
{% endstepper %}

## Examples


# Recovery

`RootRecovery`, accessed via `FMTCRoot.recovery`, allows access to the bulk download recovery system, which is designed to allow rescue (salvation and restarting) of failed downloads when they crashed due to an unexpected event.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootRecovery-class.html>" %}

{% code fullWidth="false" %}

```dart
// List all recoverable regions, and whether each one has failed
await FMTCRoot.recovery.recoverableRegions; 
// List all failed downloads
await FMTCRoot.recovery.recoverableRegions.failedOnly; 
// Retrieve a specific recoverable region by ID
await FMTCRoot.recovery.getRecoverableRegion();
// Safely remove the specified recoverable region
await FMTCRoot.recovery.cancel(); 
```

{% endcode %}

## `RecoveredRegion`

`RecoveredRegion`s are wrappers containing recovery & some downloadable region information, around a `DownloadableRegion`.

Once a `RecoveredRegion` has been retreived, it contains the original `BaseRegion` in the `region` property.

To create a `DownloadableRegion` using the other available information with a provided `TileLayer`, use `toDownloadable`.

{% hint style="success" %}
A `RecoveredRegion` (and a `DownloadableRegion` generated from it) will point to only any remaining, un-downloaded tiles from the failed download.

The `start` tile will be adjusted from the original to reflect the progress of the download before it failed, meaning that tiles already successfully cached (excluding buffered) will not be downloaded again, saving time and network transfers.\
The `end` tile will be either the original, or the maximum number of tiles normally in the region (which will have no resulting difference than `null`, but allows for a quick estimate of the number of remaining tiles to be made without needing to re`check` the entire region).
{% endhint %}


# Testing Tile Server

A miniature tile server, intended to test and calibrate FMTC, has been included in the project.

{% hint style="success" %}
Avoid making too many costly and slow requests to your chosen tile server during development by using this miniature tile server!
{% endhint %}

For internal testing and development purposes, it also doubles down as a handy way to test your application without making too many costly and slow requests to your chosen tile server. When in use with the example application, it can handle over 2000 tiles/second.

It is a very simple web HTTP server written in Dart, that responds to all\* requests with a tile. There is a theoretically 90% chance that this tile will be a specific land tile, and a 10% chance that it will be a sea tile - designed to test the sea tile skipping functionality. *There are only these two tiles - it is not a full tile server.*

<div><figure><img src="/files/0UOPPp6ilSV4hB74oT8u" alt="" width="128"><figcaption><p><em>the</em> Land Tile<br>90% chance</p></figcaption></figure> <figure><img src="/files/AIdxfAVSp3u7lE9FwoT2" alt="" width="128"><figcaption><p><em>the</em> Sea Tile<br>10% chance</p></figcaption></figure></div>

To use this tile server:

{% hint style="info" %}
The tile server is hardcoded to use standard HTTP port 7070 to serve content, which is usually free. Other programs must not be using this port.
{% endhint %}

1. Download/compile & start the tile server (no permanent installation required)
   * On Windows or Linux\
     Download a copy of the latest '\<platform>-ts' artifact from GitHub Actions, and run the executable inside: <https://nightly.link/JaffaKetchup/flutter_map_tile_caching/workflows/main/main>
   * On other platforms\
     Clone the [FMTC GitHub repository](https://github.com/JaffaKetchup/flutter_map_tile_caching/) to your device, then run '/tile\_server/bin/tile\_server.dart' manually
2. Use the following URL to connect to it
   * From the local device: `http://localhost:7070/{z}/{x}/{y}.png`
   * From the same network (on another device): `http://<your-local-ip>:7070/{z}/{x}/{y}.png`\
     To find your local IP address, follow the [instructions for your OS here](https://www.avast.com/c-how-to-find-ip-address)
3. Control the tile server using keyboard key presses in the console window
   * `q`: Release port 7070 and quit the executable
   * UP arrow: Increase the artificial delay between request and response by 2ms
   * DOWN arrow: Decrease the artificial delay between request and response by 2ms


# Import/Export

FMTC allows stores (including all necessary tiles and metadata) to be exported to an 'archive'/a standalone file, then imported on the same or a different device!

{% hint style="warning" %}
Before using FMTC, especially to bulk download or import/export, ensure you comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

For example, OpenStreetMap's tile server forbids bulk downloading: <https://operations.osmfoundation.org/policies/tiles>. And Mapbox has restrictions on importing/exporting from outside of the user's own device.

For testing purposes, check out the testing tile server included in the FMTC project: [Testing Tile Server](/usage/bulk-downloading/testing-tile-server).
{% endhint %}

{% hint style="info" %}
FMTC does not support exporting tiles to a raw Z/X/Y directory structure with image files that can be read by other programs.
{% endhint %}

For example, this can be used to create backup systems to allow users to store maps for later off-device, sharing/distribution systems, or to distribute a preset package of tiles to all users without worrying about managing IO or managing assets, and still allowing users to update their cache afterward!

***

External functionality is accessed via `FMTCRoot.external('~/path/to/file.fmtc')`.

The path should only point to a file. When used with `export`, the file does not have to exist. Otherwise, it should exist.

{% hint style="warning" %}
The path must be accessible to the application. For example, on Android devices, it should not be in external storage, unless the app has the appropriate (dangerous) permissions.

On mobile platforms (/those platforms which operate sandboxed storage), it is recommended to set this path to a path the application can definitely control (such as app support), using a path from 'package:path\_provider', then share it somewhere else using the system flow (using 'package:share\_plus').
{% endhint %}


# Exporting

The `export()` method copies the stores, along with all necessary tiles, to an archive at the specified location (creating it if non-existent, overwriting it otherwise), in the FMTC (.fmtc) format.

{% hint style="warning" %}
The specified stores must contain at least one tile.
{% endhint %}

{% hint style="warning" %}
Archives are backend specific. They cannot necessarily be imported by a backend different to the one that exported it.
{% endhint %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootExternal/export.html>" %}

```dart
await FMTCRoot.external('~/path/to/file.fmtc').export(['storeName']);
```


# Importing

The `import()` method copies the specified archive to a temporary location, then opens it and extracts the specified stores (or all stores if none are specified) & all necessary tiles, merging them into the in-use database. The specified archive must exist, must be valid, and should contain all the specified stores, if applicable.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootExternal/import.html>" %}

There is no support for directly overwriting the in-use database with the archived database, but this may be performed manually while FMTC is uninitialised.

{% hint style="warning" %}
There must be enough storage space available on the device to duplicate the entire archive, and to potentially grow the in-use database.

This is done to preserve the original archive, as this operation writes to the temporary archive. The temporary archive is deleted after the import has completed.
{% endhint %}

```dart
final importResult =
    await FMTCRoot.external('~/path/to/file.fmtc').import(['storeName']);
```

The returned value is complex. See the API documentation for more details:

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/ImportResult.html>" %}

## Conflict Resolution Strategies

If an importing store has the same name as an existing store, a conflict has occurred, because stores must have unique names. FMTC provides 4 resolution strategies:

* `skip`\
  Skips importing the store
* `replace`\
  Deletes the existing store, replacing it entirely with the importing store
* `rename`\
  Appends the current date and time to the name of the importing store, to make it unique
* `merge`\
  Merges the two stores' tiles and metadata together

In any case, a conflict between tiles will result in the newer (most recently modified) tile winning (it is assumed it is more up-to-date).

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/ImportConflictStrategy.html>" %}

## List Stores

If the user must be given a choice as to which stores to import (or it is helpful to know), and it is unknown what the stores within the archive are, the `listStores` getter will list the available store names without performing an import.

{% hint style="warning" %}
The same storage pitfalls as `import` exist. `listStores` must also duplicate the entire archive.
{% endhint %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootExternal/listStores.html>" %}


# flutter\_map\_tile\_caching

A plugin for 'flutter\_map' providing advanced offline functionality

[![pub.dev](https://img.shields.io/pub/v/flutter_map_tile_caching.svg?label=Latest+Version)](https://pub.dev/packages/flutter_map_tile_caching) [![stars](https://badgen.net/github/stars/JaffaKetchup/flutter_map_tile_caching?label=stars\&color=green\&icon=github)](https://github.com/JaffaKetchup/flutter_map_tile_caching/stargazers) [![likes](https://img.shields.io/pub/likes/flutter_map_tile_caching?logo=flutter)](https://pub.dev/packages/flutter_map_tile_caching/score)        [![Open Issues](https://badgen.net/github/open-issues/JaffaKetchup/flutter_map_tile_caching?label=Open+Issues\&color=green)](https://github.com/JaffaKetchup/flutter_map_tile_caching/issues) [![Open PRs](https://badgen.net/github/open-prs/JaffaKetchup/flutter_map_tile_caching?label=Open+PRs\&color=green)](https://github.com/JaffaKetchup/flutter_map_tile_caching/pulls)

## Highlights

<table data-card-size="large" data-view="cards" data-full-width="false"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><p><mark style="color:blue;">◉</mark> 📲</p><p><strong>Integrated Caching × Bulk Downloading</strong></p></td><td>Get both dynamic browse caching that works automatically as the user browses the map, and bulk downloading to preload regions onto the user's device, all in one convenient, integrated API!</td><td><ul><li><a data-footnote-ref href="#user-content-fn-1">Multi-cache ('store') support</a> with <a data-footnote-ref href="#user-content-fn-2">minimized tile duplication</a></li><li><a data-footnote-ref href="#user-content-fn-3">Download any shape of area</a></li><li><a data-footnote-ref href="#user-content-fn-4">Automatic sea tile skipping</a></li><li><a data-footnote-ref href="#user-content-fn-5">Super-controllable downloads</a></li><li><a data-footnote-ref href="#user-content-fn-6">Optional download rate limiting</a></li></ul></td><td></td></tr><tr><td><p><mark style="color:red;">◉</mark> 🏃</p><p><strong>Ultra-fast &#x26; Performant</strong></p></td><td>No need to bore your users to death anymore! Bulk downloading is super-fast, and can even reach speeds of over 1000 tiles per second<a data-footnote-ref href="#user-content-fn-7">*</a>. Existing cached tiles can be displayed on the map almost instantly.</td><td><ul><li>Multi-threaded setup to minimize load on main thread, even when browse caching</li><li>Streamlined internals to reduce memory consumption</li><li>Successfully downloaded tiles aren't redownloaded when an unexpectedly failed download is recovered</li></ul></td><td></td></tr><tr><td><p><mark style="color:green;">◉</mark> 🧩</p><p><strong>Import &#x26; Export</strong></p></td><td>Export and share stores, then import them later, or on other devices! You could even remote control your organization's devices, by pushing tiles to them, keeping your tile requests (&#x26; costs) low!</td><td></td><td></td></tr><tr><td><p><mark style="color:purple;">◉</mark> 💖</p><p><strong>Quick To Implement &#x26; Easy To Experiment</strong></p></td><td>A basic caching implementation can be setup in four quick steps, and shouldn't even take 5 minutes to set-up. Check out our <a data-mention href="/pages/ZLHYkAJpLD5h8yB2SNyt">/pages/ZLHYkAJpLD5h8yB2SNyt</a> instructions.</td><td>Ready to experiment with bulk downloading, but don't want to make costly and slow tile requests? Check out the testing tile server included in the FMTC project: <a data-mention href="/pages/MwEjeVx1V910UW5bCitN">/pages/MwEjeVx1V910UW5bCitN</a>!</td><td></td></tr></tbody></table>

### Trusted by many

In addition to our generous [supporters](/v9/supporters), FMTC is also trusted and [used by businesses](#user-content-fn-8)[^8] all around the world:

<table data-card-size="large" data-view="cards"><thead><tr><th align="center"></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td align="center"><a href="https://www.pitcherogps.com/"><strong>Pitchero GPS</strong></a></td><td><a href="/files/Lj7B7oc9iqnvyLOfgB8h">/files/Lj7B7oc9iqnvyLOfgB8h</a></td><td><a href="https://www.pitcherogps.com ">https://www.pitcherogps.com </a></td></tr><tr><td align="center"><a href="https://gps.lafayette.se/"><strong>Lafayette GPS</strong></a></td><td><a href="/files/dTfr056w1lZzLW3ntbP7">/files/dTfr056w1lZzLW3ntbP7</a></td><td><a href="https://gps.lafayette.se/">https://gps.lafayette.se/</a></td></tr><tr><td align="center"><a href="https://nventive.com/"><strong>nventive</strong></a></td><td><a href="/files/sm2lNTzY0kMUIoAh3trl">/files/sm2lNTzY0kMUIoAh3trl</a></td><td><a href="https://nventive.com/">https://nventive.com/</a></td></tr><tr><td align="center">...and many more!</td><td></td><td></td></tr></tbody></table>

### How can FMTC elevate my app to the next level?

Too easy :smile:! Take a look at Google Maps, or Strava, or whichever other app of your choice.

<details>

<summary>All I see are rectangles. I don't want rectangles.</summary>

Whether it's walking along a remote winding river using the [Line region](/v9/bulk-downloading/regions#poly-line), downloading all of central London ready for that weekend exploration using the [Circle region](/v9/bulk-downloading/regions#circle) (roaming fees + maps gets expensive fast!), or tracking your belongings across a vast, shapeless space using the [Custom Polygon region](/v9/bulk-downloading/regions#custom-polygon), FMTC has your user's back - but not all of their storage space!

</details>

<details>

<summary>There's too much blue in my map. Can I avoid storing useless sea tiles?</summary>

With Sea Tile Skipping, you can avoid storing those unneccessary tiles of sea, then use the map client's functonality to just paint the spaces the same color as the sea. This also preserves sea tiles that aren't so empty after all - that boat path could come in handy. Just another way FMTC keeps your user's phone bloat free ;)

</details>

<details>

<summary>I need to download something else for a moment. Do I really have to stop the entire download and start again?</summary>

Not with FMTC! Downloads can be paused and resumed at any time, and with Download Recovery, downloads that stopped unexpectedly can be started right from where they left off, without your user even knowing something went wrong.

</details>

<details>

<summary>I wonder how much it costs the app developers?</summary>

FMTC supports bulk downloading from any tile server\*[^9], so you can choose whichever one suits you best.

Our browse caching mechanism doesn't result in any extra requests to the tile server, and in fact can reduce costs by serving tiles to users from their local cache without cost. Or, if you're running your own server, you can reduce the strain on it, keeping it snappy with fewer resources!

Downloads can be rate limited to avoid running up to the server's rate limit or excess fee.

And with export/import functionality, user's can download regions just once, then keep the download themselves for another time. Or, you can provide a bundle of tiles to all your user's, while still allow it to be updated per-user in future!\*[^10]

</details>

***

## Supporting Me

This project is wholly open source and funded by generous supporters like you! Any amount you can spare that you think FMTC deserves is hugely appriciated, and means a lot to me.

{% content-ref url="/pages/yhLUvZi1rccWYQwir6gB" %}
[Supporters](/v9/supporters)
{% endcontent-ref %}

## (Proprietary) Licensing

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution.
{% endhint %}

{% content-ref url="/pages/Qr8MaFUrPCkW89RA1sBp" %}
[(Proprietary) Licensing](/v9/proprietary-licensing)
{% endcontent-ref %}

## Get Help

Not quite sure about something? No problem, I'm happy to help!

Please get in touch via the correct method below for your issue, and I'll be there to help ASAP!

* For bug reports & feature requests: [GitHub Issues](https://github.com/JaffaKetchup/flutter_map_tile_caching/issues)
* For implementation/general support: The *#plugin* channel on the [flutter\_map Discord server](https://github.com/fleaflet/flutter_map#discord-server)
* For other inquiries and licensing: <fmtc@jaffaketchup.dev>

[^1]: Keep your users' tiles organized, and even let them control what goes where!

[^2]: Tiles can belong to multiple stores, and tiles from different sources (template URLs) can be stored in a single store!

[^3]: Download rectangles, circles, line-based, and any other freehand polygon!

[^4]: Avoid downloading redundant, waste-of-space tiles that cover oceans, with this unique functionality, and bless your users with the gift of more usable capacity for useful maps!

[^5]: Bulk downloads come with the ability to pause, resume, and cancel downloads mid-way! Give your users choice.

[^6]: Downloading from a server with a rate limit? No problem: just enable rate limiting and we'll do our best to stick to it!

[^7]: Speed is very dependent on tile server ability, network delays, and device processing power. Actual speed is likely to be considerably lower.

    1500 tiles was tested from the included testing tile server running on localhost, on a Windows 11 (Intel 12th Gen i7-12700H CPU & DDR5 4800MHz RAM) with 10 downloading threads & a buffer of 500 tiles.

[^8]: Disclaimer: these projects/businesses use FMTC licensed under an [alternative proprietary license](#proprietary-licensing).

[^9]: Those compatible with flutter\_map. Some tile server's may forbid bulk downloading.

[^10]: Some tile servers may forbid this activity. Check your tile server's ToS.


# Is FMTC Right For Me?

*In a one word answer: Yes.*

FMTC aims to provide all the functionality you will need for advanced offline mapping in your app, and the unique features that will help set your app apart from the competition, in a way that requires little knowledge of the internals of 'flutter\_map' and other caching fundamentals, and little effort from you (for most setups).

However, this doesn't mean there aren't any other options to consider! The flutter\_map documentation gives a good overview of the different types of caching.

{% embed url="<https://docs.fleaflet.dev/tile-servers/offline-mapping>" %}

In general, there's a few reasons why I wouldn't necessarily recommend using FMTC:

* You're not planning to make use of bulk downloading or import/export functionality
* You don't need the fine grained control of stores (and their many-to-many relationship with tiles that keeps duplication minimal across them)
* You want to ship every user a standardized tileset, and you don't really need other functionality after that point

Although FMTC will still handle all these situations comfortably, other options may be better suited, and/or more lightweight.

**FMTC is an all-in-one solution. It specialises in the functionalities which are difficult and time-consuming to get right (such as bulk downloading), and their integration with the essential function of browse caching, through a clean, unified API, and a clean, decluttered, and fast backend.**

**Other libraries or DIY solutions may not be all-in-one, but they may be all that's required for your app.** Caching alone is not difficult or time consuming to setup yourself, either through a custom `TileProvider` backed by a non-specialised image caching provider such as '[cached\_network\_image](https://pub.dev/packages/cached_network_image)', or the other browse-caching-only flutter\_map plugin '[flutter\_map\_cache](https://pub.dev/packages/flutter_map_cache)' if you need to save even more time at the expense of more external dependencies.

{% hint style="warning" %}
Another consideration for non-GPL-licensed projects is abiding by FMTC's GPL license. This is especially true for proprietary products.

For more information about licensing, please see [(Proprietary) Licensing](/v9/proprietary-licensing).
{% endhint %}

If you're still not sure, please get in touch: [flutter\_map\_tile\_caching](/v9#get-help)! I'm always happy to offer honest guidance :)


# Supporters

## Supporting Me

Hi there 👋 My name is Luka, but I go by JaffaKetchup!

I'm currently in full-time education in the UK studying Computer Science, Geography, and Mathematics, and have been building my software development skills alongside my education for many years. I specialise in the Flutter and Dart technologies to build cross-platform applications, and have over 4 years of experience both from a hobby and commercial standpoint.

I've worked as a senior developer with a small team at WatchEnterprise to develop their Flutter-based WatchCrunch social media app for Android & iOS.

I'm one of a small team of maintainers for Flutter's №1 non-commercially aimed mapping library 'flutter\_map', for which we make internal contributions, regulate and collaborate with external contributors, and offer support to a large community. I also personally develop a multitude of extension libraries, such as 'flutter\_map\_tile\_caching'. In addition, I regularly contribute to OpenStreetMap, and continously improve my skills with personal experimental projects.

I'm eager to earn other languages, and more than happy to talk about anything software development related!

Sponsorships & donations allow me to further my education, whilst spening even more time developing the open-source projects that you use & love. I'm grateful for any amount you can spare, all support means a lot to me :) If you can't support me financially, please consider leaving a star and a like on projects that worked well for you.

I'm extremely greatful for any amount you can spare!

{% embed url="<https://github.com/sponsors/JaffaKetchup>" %}
Sponsor Me via GitHub Sponsors
{% endembed %}

## Supporters

Many thanks to all my supporters, donations of all sizes mean a lot to me, and encourage and enable me to continue my work on FMTC and other open-source projects.

In no particular order:

* [@tonyshkurenko](https://github.com/tonyshkurenko)
* [@Mmisiek](https://github.com/Mmisiek)
* [@huulbaek](https://github.com/huulbaek)
* [@andrewames](https://github.com/andrewames)
* [@ozzy1873](https://github.com/ozzy1873)
* [@eidolonFIRE](https://github.com/eidolonFIRE)
* [@weishuhn](https://github.com/weishuhn)
* [@mohammedX6](https://github.com/mohammedX6)
* [@quentinchaignaud](https://github.com/quentinchaignaud)
* [@Mayb3Nots](https://github.com/Mayb3Nots)
* [@T-moz](https://github.com/T-moz)
* [@micheljung](https://github.com/micheljung)
* *+ more anonymous or private donors*

And also a thank you to the businesses listed in [flutter\_map\_tile\_caching](/v9#trusted-by-many), and more, for abiding by the GPL license of this repository.


# (Proprietary) Licensing

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution.
{% endhint %}

*I am not a lawyer, and this information is to the best of my understanding only. You are urged to read the license yourself for a thorough understanding.*

This project is released under GPL v3. For detailed information about this license, see <https://www.gnu.org/licenses/gpl-3.0.en.html>. [choosealicense.com](https://choosealicense.com/licenses/gpl-3.0/) summarises the license with the following paragraph:

> Permissions of this strong copyleft license are conditioned on **making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license**. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights.

Essentially, whilst you can use this code within commercial projects, they must not be proprietary - they incorporate this 'licensed work' so they must be available under the same license. You must distribute your source code (at least on request) (under the same GPL v3 license) to anyone who uses your program.

I learnt (and am still learning) to code with free, open-source software due to my age and lack of money, and for that reason, I believe in promoting open-source wherever possible to give equal opportunities to everybody, no matter their age, financial position, or any other characteristic. I'm not sure it's fair for commercial & proprietary applications to use software made by people for free out of generosity without giving back to the ecosystem or maintainer(s).\
On the other hand, I recognise that commercial businesses may want to use my projects for their own proprietary applications, and are happy to support me, and I am also trying to make a small amount of money from my projects, by donations and by selling licenses!

Therefore, if you would like a license to use this software within a proprietary application, I am willing to sell a (preferably yearly) license. If this seems like what you'd be interested in, please do not hesitate to get in touch at <fmtc@jaffaketchup.dev>. Please include details of your project if you can, and the approximate scale/audience for your app; I try to find something that works for everyone, and I'm happy to negotiate! If you're a non-profit organization, I'm happy to also offer an alternative license for free\*[^1]!

{% hint style="info" %}
Want to see who else uses FMTC? Check out [flutter\_map\_tile\_caching](/v9#trusted-by-many)!
{% endhint %}

[^1]: Decision will be case dependent. Please get in touch, and I'll be happy to talk!


# Quickstart

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution. For more information, please see [(Proprietary) Licensing](/v9/proprietary-licensing).
{% endhint %}

This page guides you through a simple, fast setup of FMTC that just enables basic browse caching, without any of the cool features that you can discover throughout the rest of this documentation.

## 1. [Install](/v9/get-started/installation)

Depend on the latest version of the package from pub.dev, then import it into the appropriate files of your project.

{% code title="Console/Terminal" %}

```sh
flutter pub add flutter_map_tile_caching
```

{% endcode %}

```dart
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
```

## 2. [Initialise](/v9/general/initialisation)

Perform the startup procedure to allow usage of FMTC's APIs and allow FMTC to spin-up the underlying connections & systems.

Here, we'll use the built-in, default 'backend' storage, which uses [ObjectBox](https://pub.dev/packages/objectbox). We'll perform the intialisation just before the app starts, so we can be sure that it will be ready and accessible throughout the app, at any time.

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">import 'package:flutter/widgets.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

Future&#x3C;void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    
<strong>    await FMTCObjectBoxBackend().initialise(...);
</strong>    
    // ...
    
    runApp(MyApp());
}
</code></pre>

## 3. [Create a store](/v9/stores-and-roots/roots-and-stores#without-automatic-creation)

Create a container that is capable of storing tiles, and can be used to [browse cache](#user-content-fn-1)[^1] and bulk download.

Here, we'll create one called 'mapStore', directly after initialisation. Any number of stores can be created, at any point!

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">    // ...
    
<strong>    await FMTCStore('mapStore').manage.create();
</strong>    
    // ...
</code></pre>

## 4. [Connect to 'flutter\_map'](/v9/stores-and-roots/fm-integration)

Add FMTC's specialised `TileProvider` to the `TileLayer`, to enable browse caching, and retrieval of tiles from the specified store.

{% hint style="warning" %}
Double check that the name of the store specified here is the same as the store created above!
{% endhint %}

<pre class="language-dart" data-title="map_view.dart"><code class="lang-dart">import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

TileLayer(
    urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
    userAgentPackageName: 'com.example.app',
<strong>    tileProvider: FMTCStore('mapStore').getTileProvider(),
</strong>    // Other parameters as normal
),
</code></pre>

## 5. Wow! Look at that caching...

{% hint style="success" %}
You should now have a basic working implementation of FMTC that caches tiles for you as you browse the map!

There's a lot more to discover, from store management to bulk downloading, and from statistics to exporting/importing.
{% endhint %}

{% hint style="warning" %}
Before using FMTC, especially to bulk download or import/export, ensure you comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

For example, OpenStreetMap's tile server forbids bulk downloading: <https://operations.osmfoundation.org/policies/tiles>. And Mapbox has restrictions on importing/exporting from outside of the user's own device.

For testing purposes, check out the testing tile server included in the FMTC project: [Testing Tile Server](/v9/bulk-downloading/testing-tile-server).
{% endhint %}

[^1]: This caching occurs automatically as the map is moved by the user, and new tiles load.


# Installation

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing an application that isn't licensed under GPL, this affects you and your application's legal right to distribution. For more information, please see [(Proprietary) Licensing](/v9/proprietary-licensing).
{% endhint %}

{% hint style="success" %}
Looking to start using FMTC in your project? Check out the [Quickstart](/v9/get-started/quickstart) guide!
{% endhint %}

## Depend On

### From [pub.dev](https://pub.dev/packages/flutter_map_tile_caching)

This is the recommended method of installing this package as it ensures you only receive the latest stable versions, and you can be sure pub.dev is reliable.

Just import the package as you would normally, from the command line:

```shell
flutter pub add flutter_map_tile_caching
```

### From [github.com](https://github.com/JaffaKetchup/flutter_map_tile_caching)

If you urgently need the latest version, a specific branch, or a specific fork, you can use this method.

{% hint style="info" %}
Commits available from Git (GitHub) may not be stable. Only use this method if you have no other choice.
{% endhint %}

First, add the normal dependency following the [#from-pub.dev](#from-pub.dev "mention") instructions. Then, add the following lines to your pubspec.yaml file under the `dependencies_override` section:

{% code title="pubspec.yaml" %}

```yaml
dependency_overrides:
    flutter_map_tile_caching:
        git:
            url: https://github.com/JaffaKetchup/flutter_map_tile_caching.git
            # ref: a commit hash, branch name, or tag (otherwise defaults to master)
```

{% endcode %}

## Import

After installing the package, import it into the necessary files in your project:

```dart
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
// You'll also need to import flutter_map and (likely) latlong2 seperately
```

{% hint style="success" %}
Also ensure you've followed flutter\_map's installation instructions!
{% endhint %}


# Example Application

This package contains a full example application - prebuilt for Android and Windows by GitHub Actions - showcasing the most important features of this package and its modules.

{% hint style="info" %}
The example app isn't intended for beginners or as a starting point for a project. It is intended for evaluation purposes, to discover FMTC's capabilities, and how it might be implemented into an app.

To start using FMTC in your own app, please check out the [Quickstart](/v9/get-started/quickstart) guide instead.
{% endhint %}

{% hint style="success" %}
The example application pairs perfectly with the testing tile server included in the FMTC project: [Testing Tile Server](/v9/bulk-downloading/testing-tile-server)!
{% endhint %}

## Prebuilt Artifacts

If you can't build from source for your platform, our GitHub Actions CI system compiles the example app to artifacts for Windows and Android, which just require unzipping and installing the .exe or .apk found inside.

{% hint style="info" %}
Note that these artifacts are built automatically from the ['master' branch](https://github.com/fleaflet/flutter_map), so may not reflect the the latest release on pub.dev.
{% endhint %}

{% embed url="<https://nightly.link/JaffaKetchup/flutter_map_tile_caching/workflows/main/main>" %}
Latest Build Artifacts (thanks [nightly](https://nightly.link/))
{% endembed %}

## Build From Source

If you need to use the example app on another platform, you can build from source, using the 'example' directory of the repository.


# v8 -> v9 Migration

{% hint style="success" %}
v9 is a complete rewrite of FMTC internals, written over hundreds of hours. It focuses on:&#x20;

* improved future maintainability by modularity
* improved stability & performance across the board
* support of 'tiles across stores': reduced duplication

Check out the CHANGELOG: <https://pub.dev/packages/flutter_map_tile_caching/changelog>! This page only covers breaking changes, not feature additions and fixes.

Please consider donating: [flutter\_map\_tile\_caching](/v9#supporting-me)! Any amount is hugely appriciated!
{% endhint %}

These migration instructions will not cover every scenario, because the number of breaking changes is so high. I've done my best to organise them into categories.

## Universal

{% hint style="warning" %}
Stores and roots from previous versions will be incompatible with v9, and will not be migratable.

This is because the underlying storage technology has chnaged from Isar to ObjectBox (in the default instance, at least).

Therefore, before publishing a version of your app with v9, it may be beneficial to notify users of the upcoming change, so the sudden dissappearance of their cached data is not unexpected.

Removal of the old cache will need to be performed manually.
{% endhint %}

<details>

<summary>Removed the <code>FlutterMapTileCaching</code>/<code>FMTC</code> object, in favour of direct usage of <code>FMTCStore</code> and <code>FMTCRoot</code> (which replace <code>StoreDirectory</code> &#x26; <code>RootDirectory</code>)</summary>

Much of the configuration and state management performed by the `FlutterMapTileCaching` top-level object singleton, and it's close relatives, were transferred to the backend, and as such, there is no longer a requirement for these objects.

Additionally, the name 'directory' has been outdated for a while. Therefore, these changes were merged into one.

To migrate, follow these patterns:

{% code title="Previous" %}

```dart
// Get a store directory
final StoreDirectory oldStore = FlutterMapTileCaching.instance('storeName'); // (or `FMTC.`)

// Access the root statistics
final RootDirectory oldRoot = FlutterMapTileCaching.instance.rootDirectory; // (or `FMTC.`)
final RootStats oldRootStats = oldRoot.stats;
```

{% endcode %}

<pre class="language-dart" data-title="Migrated"><code class="lang-dart">// Get a store
<strong>final FMTCStore newStore = FMTCStore('storeName');
</strong>
// Access the root statistics
<strong>final RootStats newRootStats = FMTCRoot.stats;
</strong>// `FMTCRoot` must now be used immediately, because it is not an object instance
</code></pre>

See below for information about migrating initialisation.

</details>

<details>

<summary>Changed the method of initialisation &#x26; error handling</summary>

Due to the removal of the `FMTC` object, and introduction of multiple-backend support, initialisation is now performed directly on a backend. The backend then creates a link between itself and its implementation to the abstracted convienience methods and front.

Additionally, error handling has been improved throughout FMTC, and is now more consitent and stable, doesn't rely on callbacks, and error `StackTrace`s include more useful information.

For the default, built-in backend, migration is simple:

{% code title="Previous" %}

```dart
await FlutterMapTileCaching.initialise(
    errorHandler: (FMTCInitialisationException e) {},
);
```

{% endcode %}

{% code title="Migrated" %}

```dart
try {
    await FMTCObjectBoxBackend().initialise();
} catch (error, stackTrace) {
    // Improved error handling
}
```

{% endcode %}

For more information, see [Initialisation](/v9/general/initialisation) & [Error Handling](/v9/general/error-handling).

</details>

<details>

<summary>Removed support for synchronous operations (and renamed asynchronous operations to reflect this)</summary>

These were incompatible with the new `Isolate`d `FMTCObjectBoxBackend`, they've been removed, in favour of backends implementing their own `Isolate`ion as well.

There is no direct migration instructions, as the correct new solution is case-dependent. In non-widget environments, use asynchronous techniques. In widget builds, make use of `FutureBuilder`s.\
However, the members have all been renamed in the same form: `*Async` is now just `*`.

</details>

## Bulk Downloading

<details>

<summary>Refactored <code>DownloadableRegion</code> &#x26; removed <code>RegionType</code></summary>

`DownloadableRegion` no longer contains the outline `points` of the `BaseRegion` it was formed from. It also no longer contains `parallelThreads`, `preventRedownload`, and `seaTileRemoval`: these are now configurable at download-time. `errorHandler` has been removed altogether.

Additionally, `DownloadableRegion` now makes use of sealed typing by using the type argument to contain the type of `BaseRegion`, so `RegionType` has become redundant and been removed.

</details>

## Plugins

<details>

<summary>Background downloading plugin deprecated without replacement</summary>

'package:fmtc\_plus\_background\_downloading' has been deprecated without replacment.

It was becoming increasing unstable, and depended on unmaintained, unstable, and small packages. It also only supported Android, and did not properly work in many cases.

Therefore, the plugin has been deprecated without replacement. The functionality may be re-introduced into the core at a later point.

</details>

<details>

<summary>Sharing plugin deprecated, <code>external</code> replacement functionality introduced into core</summary>

'package:fmtc\_plus\_sharing' has been deprecated, and the importing/exporting functionality introduced into the core, as [External](/v9/external/introduction).

There is no replacement for the GUI/file picker functionality, to keep core dependencies minimized.

</details>

## Statistics

<details>

<summary><code>RootStats</code> &#x26; <code>StoreStats</code> members are no longer prefixed with 'root' &#x26; 'store'/'cache'</summary>

These terms were redundant, and have been removed.

For example, `rootSize` is now just `size`, and `cacheHits` is now just `hits`.

</details>

<details>

<summary>Replaced <code>RootStats.watchChanges</code> with <code>watchStores</code> &#x26; <code>watchRecovery</code></summary>

`watchStores` now watches for changes in statistics (which should change whenever tiles are changed), and changes in metadata in the specified stores. `watchRecovery` is now used to watch for changes to the recovery system.

This was done to simplify the APIs and allow for the removal of `StoreParts` (which has been removed without deprecation).

</details>

## Miscellaneous

<details>

<summary>Removed <code>FMTCSettings</code></summary>

`FMTCSettings` have been split apart. `databaseMaxSize` now has an equivalent in the configuration of the `FMTCObjectBoxBackend`, `databaseCompactCondition` has been removed without replacement, and `defaultTileProviderSettings` has been removed in favour of a singleton-ish `FMTCTileProviderSettings`.

`FMTCTileProviderSettings` may now be set as a global instance (which works the same as the old `FMTCSettings` option) just by constructing it. Alternatively, one can be constructed without setting the global instance by setting `setInstance` `false` in the arguments.

</details>


# Initialisation

FMTC relies on a self-contained 'environment', called a [backend](/v9/general/backends), that requires initialisation (and configuration) before it can be used. This allows the backend to start any necessary seperate threads/isolates, load any prerequisites, and open and maintain a connection to a database. This environment/backend is then accessible internally through a(\*[^1]) singleton, so initialisation is not required again.

## Initialisation

Initialisation should be performed before any other FMTC or backend methods are used, and so it is usually placed just before `runApp`, in the `main` method. This shouldn't have any significant effect on application startup time.

{% hint style="warning" %}
If initialising in the `main` method before `runApp` is called, ensure you also call `WidgetsFlutterBinding.ensureInitialised()` prior to the backend initialisation.
{% endhint %}

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">import 'package:flutter/widgets.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

Future&#x3C;void> main() async {
    WidgetsFlutterBinding.ensureInitialized();   
    
    try {
<strong>        await FMTCObjectBoxBackend().initialise(...); // The default/built-in backend
</strong>    } catch (error, stackTrace) {
        // See below for error/exception handling
    }
    
    // ...
    
    runApp(MyApp());
}
</code></pre>

{% hint style="danger" %}
Do not call any other FMTC methods before initialisation. Doing so will cause a `RootUnavailable` error to be thrown.
{% endhint %}

{% hint style="danger" %}
Do not attempt to initialise the same backend multiple times, or initialise multiple backends simultaenously. Doing so will cause a `RootAlreadyInitialised` error to be thrown.
{% endhint %}

{% hint style="warning" %}
Avoid using FMTC in a seperate thread/`Isolate`. FMTC backends already make extensive use of multi-threading to improve performance.

If it is essential to use FMTC in a seperate thread, ensure that the initialisation is called in the thread where it is used. Be cautious of using FMTC manually across multiple threads simultaneously, as backends may not properly support this, and unexpected behaviours may occur.
{% endhint %}

## Uninitialisation

It is also possible to un-initialise FMTC and the current backend. This should be rarely required, but can be performed through the `uninitialise` method of the backend if required. Initialisation is possible after manual uninitialisation.

[^1]: Internally, more than one singleton may be used in a backend, and to access a backend. However, this is beyond the scope of this page.


# Backends

{% hint style="info" %}
This page covers more advanced FMTC usage, and is not appropriate for beginners or simple use cases.

TLDR; FMTC provides a single storage mechanism by default, named `FMTCObjectBoxBackend`.
{% endhint %}

***

FMTC supports attachment of any custom storage mechanism, through an `FMTCBackend`. This allows users to pick their favourite database engine, or conduct in-memory testing.

{% hint style="success" %}
Only one backend is built-into FMTC: the `FMTCObjectBoxBackend`. This backend uses the [ObjectBox library](https://pub.dev/packages/objectbox) to store data.
{% endhint %}

> More info coming soon...


# Error Handling

Because FMTC has complex internals that rely heavily on external factors and preconditions being met, it is important to properly anticipate, catch, and handle errors & exceptions.

Errors & exceptions in FMTC come in two primary forms:

* Errors: usually generated by FMTC & descended from `FMTCBackendError`\
  These indicate incorrect usage on the user's part - for example, an incorrect assumption such as usage of a method on a store before initialisation. These may also be non-specfic types, such as `ArgumentError`.
* Exceptions: usually generated by the depdenencies of backends, such as databases\
  These indicate some sort of unexpected failure, such as a database reaching its maximum size limit during a write operation. These are usually not generated/typed by FMTC, and so are often of types from other libraries, such as ObjectBox when using the default `FMTCObjectBoxBackend`.

{% hint style="info" %}
The difference between errors & exceptions extends beyond FMTC, and is a general concept in Dart. See [this post for more information](https://groups.google.com/a/dartlang.org/g/misc/c/lx9CXiV3o30/m/s5l_PwpHUGAJ) about the difference between `Exception`s and `Error`s.
{% endhint %}

Exceptions may be caught using standard `try`/`catch` blocks  (it's not usually recommended to catch `FMTCBackendError`s).

FMTC automatically adjusts and changes the thrown `StackTrace` to include useful additional info, and ensures that the trace is followed across the many isolates and asynchronous gaps (eg. streams) of the FMTC internals. When creating a bug report, please include the full trace, including debug info at the end, if any is available.

It can usually be assumed that the requested operation did not & will not complete when an exception is thrown from it.

## During Initialisation

One particular place where exceptions can occur more frequently is during initialisation. The code sample above includes a `try`/`catch` block to catch these errors. If an exception occurs at this point, it's likely unrecoverable (for example, it might indicate that the underlying database has been corrupted), and the best course of action is often to manually delete the FMTC root directory from the filesystem.

The default directory can be found and deleted with the following snippet (which requires 'package:path' and 'package:path\_provider':

```dart
import 'dart:io';

import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';

final dir = Directory(
  path.join(
    (await getApplicationDocumentsDirectory()).absolute.path,
    'fmtc',
  ),
);

await dir.delete(recursive: true);

// Then reinitialise FMTC
```


# Tips

{% hint style="info" %}
This page is a work in progress! Check back later for more tips and tricks.
{% endhint %}

***

{% hint style="success" %}
**Keep the number of stores small to improve performance!**

FMTC isn't internally optimized to handle large numbers of stores within the database queries and processing, and algorithms often have polynomial time complexities based on the number of stores. This is because of the feature that tiles can belong to multiple stores, and therefore a lot of extra work is required to keep track of this all.
{% endhint %}


# Introduction

FMTC uses a *root* and *stores* to structure its data.

There is generally a single root (which generally corresponds to a single [backend](/v9/general/initialisation#backends)), which contains multiple stores. Cached tiles can belong to multiple stores, which keeps duplication minimized.

{% hint style="warning" %}
The structures use the ambient backend when a method is invoked on it, not at construction time.

Therefore, it is possible to construct an `FMTCStore`/`FMTCRoot` (see below) before initialisation, but 'using' it will throw `RootUnavailable`.
{% endhint %}


# Stores

Stores contain any metadata associated with them, cached statistics, and maintain a reference to all the tiles that belong to it.

They are referenced by name, the single argument of `FMTCStore`.

{% hint style="warning" %}
Ensure names of stores are consistent across every access. "Typed"/code-generated stores are not provided, to maintain flexibility.
{% endhint %}

Construction of an `FMTCStore` object does not imply/infer that the underlying store has been created and is ready for use. Therefore, a store will require creation via its `StoreManagement` object (accessed via `FMTCStore.manage`) before it can be used.

<pre class="language-dart"><code class="lang-dart"><strong>// final store = FMTCStore('storeName');
</strong>await FMTCStore('storeName').manage.create(); // Refers to the same store as above
</code></pre>

After a store reference is constructed, the following actions can be performed with it:

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Management:</strong> <code>manage</code></td><td>Control the store: create it, delete it, rename it, etc.</td><td><a href="/pages/ri5PBUsJHrSWfQvdWb6n">/pages/ri5PBUsJHrSWfQvdWb6n</a></td></tr><tr><td><strong>Statistics:</strong> <code>stats</code></td><td>Retrieve information about the store and its contents</td><td><a href="/pages/WQ0maJmptG7ue7YiFbtN">/pages/WQ0maJmptG7ue7YiFbtN</a></td></tr><tr><td><strong>Metadata:</strong> <code>metadata</code></td><td>Access simple persistent storage, with no direct influence on FMTC's functioning</td><td><a href="/pages/X3FJe4OjIZh4wpucEFk1">/pages/X3FJe4OjIZh4wpucEFk1</a></td></tr><tr><td><em><strong>Bulk Download:</strong></em> <em><code>download</code></em></td><td>Prepare/plan, start, and manage bulk downloads</td><td><a href="/pages/4bgyDv2CUBwkhbfFwaAJ">/pages/4bgyDv2CUBwkhbfFwaAJ</a></td></tr><tr><td><em><strong>Integrate With</strong><strong> </strong><strong><code>TileLayer</code></strong></em></td><td><em>Generate a specialised <code>TileProvider</code> that allows flutter_map to access cached tiles</em></td><td><a href="/pages/qr1Ja34BhspEihyVfR5I">/pages/qr1Ja34BhspEihyVfR5I</a></td></tr></tbody></table>


# Management

`StoreManagement`, accessed via `FMTCStore().manage`, allows control over the store and its contents.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreManagement-class.html>" %}

<pre class="language-dart" data-full-width="false"><code class="lang-dart"><strong>final mgmt = FMTCStore('storeName').manage;
</strong>
await mgmt.ready; // Check whether the store exists
await mgmt.create(); // Create the store
await mgmt.delete(); // Empty tiles from the store, and delete it
await mgmt.reset(); // Empty tiles from the store, and reset hits &#x26; misses
await mgmt.rename('newStoreName'); // Change the name of the store
await mgmt.removeTilesOlderThan(DateTime.timestamp()); // Empty all tiles last modified before the specified timestamp
</code></pre>


# Statistics

`StoreStats`, accessed via `FMTCStore().stats`, allows access to cached statistics, as well as retrieval of a recent tile (as an image), and the watching over changes in the store.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreStats-class.html>" %}

<pre class="language-dart" data-full-width="false"><code class="lang-dart"><strong>final stats = FMTCStore('storeName').stats;
</strong>
await stats.all; // Retrieve the size, length, hits, and misses of this store
await stats.size; // Retrieve the total number of KiBs of all tiles' bytes (not 'real total' size)
await stats.length; // Retrieve the number of tiles belonging to this store
await stats.hits; // Retrieve the number of successful tile retrievals when browsing
await stats.misses; // Retrieve number of unsuccessful tile retrievals when browsing
await stats.tileImage(); // Retrieve the tile most recently modified in the specified store
await stats.watchChanges(); // Watch for changes to statistics (including tile events) and metadata
</code></pre>


# Metadata

`StoreMetadata`, accessed via `FMTCStore().metadata`, allows access and control over a simple peristent storage mechanism, designed for use with custom data/properties/fields tied to the store (such as a [`CacheBehavior`](/v9/stores-and-roots/fm-integration#cache-behavior) or URL template).

Data is interpreted in key-value pair form, where both the key and value are `String`s. Internally, the default backend stores it as a flat JSON structure. The metadata is stored directly on the store: if the store is deleted, it is deleted, and an exported store will retain its metadata. More advanced requirements will require use of a seperate persistance mechanism.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreMetadata-class.html>" %}

{% hint style="info" %}
Remember that `metadata` does not have any effect on internal logic: it is simply an auxiliary method of storing any data that might need to be kept alongside a store.
{% endhint %}

<pre class="language-dart" data-full-width="false"><code class="lang-dart"><strong>final md = FMTCStore('storeName').metadata;
</strong>
await md.read; // Retrieve (all) the stored metadata
await md.set(); // Set a single key-value pair (overwriting any existing value for the key)
await md.setBulk(); // Set multiple key-value pairs (overwriting any existing value for each key)
await md.remove(); // Remove the specified key (and corresponding value)
await md.reset(); // Remove all keys (and values)
</code></pre>


# Roots

Roots contain cached statistics and recovery information.

Because there is only a single root at any time, the root is not named. Instead, it is accessed via `FMTCRoot` - and all members are static.

<pre class="language-dart"><code class="lang-dart"><strong>// final root = FMTCRoot;
</strong>final size = await FMTCRoot.stats.realSize;
</code></pre>

After the root reference is constructed, the following actions can be performed with it:

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Statistics:</strong> <code>stats</code></td><td>Retrieve information about the store and its contents</td><td><a href="/pages/UOIziKDB99IWWpU0pUid">/pages/UOIziKDB99IWWpU0pUid</a></td></tr><tr><td><strong>Recovery:</strong> <code>recovery</code></td><td>Recovery from unexpectedly failed bulk downloads</td><td><a href="/pages/QjStFkRCKXgLWD1lY19Q">/pages/QjStFkRCKXgLWD1lY19Q</a></td></tr><tr><td><em><strong>External:</strong> <code>external</code></em></td><td>Export &#x26; import store archives</td><td><a href="/pages/1fyswNKNfX7IotiQh9N8">/pages/1fyswNKNfX7IotiQh9N8</a></td></tr></tbody></table>

{% hint style="info" %}
Management of the root is not provided by `FMTCRoot`. Instead, the backend should implement any necessary methods.
{% endhint %}


# Statistics

`RootStats`, accessed via `FMTCRoot.stats`, allows access to cached statistics, as well as listing of all existing stores, and the watching over changes in multiple/all stores and the bulk download recovery system.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootStats-class.html>" %}

<pre class="language-dart" data-full-width="false"><code class="lang-dart"><strong>final stats = FMTCRoot.stats;
</strong>
await stats.storesAvailable; // List all the available/existing stores
await stats.realSize; // Retrieve the actual total size of the database in KiBs
await stats.size; // Retrieve the total number of KiBs of all tiles' bytes (not 'real total' size) from all stores
await stats.length; // Retrieve the total number of tiles in all stores
await stats.watchRecovery(); // Watch for changes to the recovery system
await stats.watchStores(); // Watch for changes in the specified (or all) stores
</code></pre>

{% hint style="info" %}
Remember that the `size` and `length` statistics in the root may not be equal to the sum of the same statistics of all available stores, because tiles may belong to many stores, and these statistics do not count any tile multiple times.
{% endhint %}


# Recovery

`RootRecovery`, accessed via `FMTCRoot.recovery`, allows access to the bulk download recovery system, which is designed to allow rescue (salvation and restarting) of failed downloads when they crashed due to an unexpected event.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootRecovery-class.html>" %}

<pre class="language-dart" data-full-width="false"><code class="lang-dart"><strong>final rec = FMTCRoot.recovery;
</strong>
await rec.recoverableRegions; // List all recoverable regions, and whether each one has failed
await rec.recoverableRegions.failedOnly; // List all failed downloads
await stats.getRecoverableRegion(); // Retrieve a specific recoverable region by ID
await stats.cancel(); // Safely remove the specified recoverable region
</code></pre>

## Restoring Usable Regions

Once a `RecoveredRegion` has been retreived, it can be converted to a standard region:

* either a `DownloadableRegion`, using `toDownloadable`\
  The `start` tile will be adjusted from the original to reflect the progress of the download before it failed, meaning that tiles already successfully cached (excluding buffered) will not be downloaded again, saving time and data!\
  The `end` tile will be either the original, or the maximum number of tiles normally in the region (which will have no resulting difference than `null`, but allows for a quick estimate of the number of remaining tiles to be made without needing to re`check` the entire region).
* or a `BaseRegion` (with the correct subtype), using `toRegion`


# flutter\_map Integration

Stores also have the method `getTileProvider()`. This is the point of integration with flutter\_map, providing browse caching through a custom image provider. This `TileProvider` can then be passed to the `TileLayer.tileProvider` parameter.

```dart
import 'package:flutter_map/flutter_map.dart';

class MapView extends StatefulWidget {
    late final tileProvider = FMTCStore('storeName').getTileProvider();
    
    Widget build(BuildContext context) {
        return FlutterMap(
            // options: MapOptions(),
            children: [
                TileLayer(
                    // Other config parameters
                    tileProvider: tileProvider,
                ),
            ],
        );
    }
}
```

{% hint style="warning" %}
Avoid getting the `TileProvider` from within the build method, especially if the widget is rebuilt frequently.

It can cause unnecessary errors and worsened performance.
{% endhint %}

## Tile Provider Settings

This method (and others) optionally take a `FMTCTileProviderSettings`. These configure the behaviour of the tile provider. Defaults to the settings specified in the [Broken mention](broken://pages/Lk3UdTwBDhabps9cswt7), or the package default (see table below) if that is not specified.

`FMTCTileProviderSettings` can take the following arguments:

<table data-card-size="large" data-view="cards"><thead><tr><th>Parameter</th><th>Description</th><th>Default</th></tr></thead><tbody><tr><td><code>behavior</code>: <a href="#cache-behavior"><code>CacheBehavior</code></a></td><td>Determine the logic used during handling storage and retrieval of browse caching</td><td><code>CacheBehavior.cacheFirst</code></td></tr><tr><td><code>cachedValidDuration</code>: <code>Duration</code></td><td>Length of time a tile remains valid, after which it must be fetched again (ignored in <code>onlineFirst</code> mode)</td><td><code>const Duration(days: 16)</code></td></tr><tr><td><code>maxStoreLength</code>: <code>int</code></td><td>Maximum number of tiles allowed in a cache store (deletes oldest tile)</td><td><code>0</code>: disabled</td></tr><tr><td><code>obscuredQueryParams</code>: <code>List&#x3C;String></code></td><td>See <a data-mention href="#obscuring-query-parameters">#obscuring-query-parameters</a></td><td><code>[]</code>: empty</td></tr></tbody></table>

### Cache Behavior

This enumerable contains 3 values, which are used to dictate which logic should be used to store and retrieve tiles from the store.

<table><thead><tr><th width="174">Value</th><th>Explanation</th></tr></thead><tbody><tr><td><code>cacheFirst</code></td><td><p>Get tiles from the local cache if possible.</p><p>Only uses the Internet if it doesn't exist, or to update it if it has expired.</p></td></tr><tr><td><code>onlineFirst</code></td><td><p>Get tiles from the Internet if possible.</p><p>Updates every cached tile every time it is fetched (ignores expiry).</p></td></tr><tr><td><code>cacheOnly</code></td><td><p>Only get tiles from the local cache, and throw an error if not found.</p><p>Recommended for dedicated offline modes.</p></td></tr></tbody></table>

### Obscuring Query Parameters

{% hint style="info" %}
Since v3, FMTC relies on URL equality to find tiles within a store during browsing. This method is therefore necessary in some cases where the URL contains query parameters.
{% endhint %}

If the URL's query parameters (the key-value pairs list found after the '?') contains a value that may change between fetches, such as an API key, use `obscuredQueryParams`.

This method strips specified keys and values from the query parameters, and avoids storing them in the database.

Pass it the list of query keys who's values need to be omitted from storage.\
For example, 'api\_key' would remove the 'api\_key', and any other characters until the next key-value pair, or the end of the URL, as seen below:

<pre><code>https://tile.myserver.com/{z}/{x}/{y}?api_key=001239876&#x26;mode=dark
<strong>https://tile.myserver.com/{z}/{x}/{y}?&#x26;mode=dark
</strong></code></pre>

{% hint style="warning" %}
Do not depend on this method to remove secret information from a URL.
{% endhint %}

{% hint style="warning" %}
Prefer sending any information (as discussed above) through the HTTP headers. This may improve performance and reliability, and can be considered good practise anyhow.
{% endhint %}

## Check If A Tile Is Cached

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/FMTCTileProvider/checkTileCached.html>" %}


# Introduction

FMTC also provides the ability to bulk download areas of maps in one-shot, known as 'regions'. There are multiple different types/shapes of regions available: [Create A Region](/v9/bulk-downloading/regions#types-of-region).

{% hint style="warning" %}
Before using FMTC, especially to bulk download or import/export, ensure you comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

For example, OpenStreetMap's tile server forbids bulk downloading: <https://operations.osmfoundation.org/policies/tiles>. And Mapbox has restrictions on importing/exporting from outside of the user's own device.

For testing purposes, check out the testing tile server included in the FMTC project: [Testing Tile Server](/v9/bulk-downloading/testing-tile-server).
{% endhint %}

Downloading is extremely efficient and fast, and uses multiple threads and isolates to achieve write speeds of hundreds of tiles per second (if the network/server speed allows). After downloading, no extra setup is needed to use them in a map (other than the usual [flutter\_map Integration](/v9/stores-and-roots/fm-integration)).

It is also simple to understand and implement:

1. [Create a region based on the user's input](/v9/bulk-downloading/regions)
2. [Convert that region into a downloadable region](/v9/bulk-downloading/prepare)\
   *↳ Optionally,* [*check the number of tiles in the region*](/v9/bulk-downloading/prepare#checking-number-of-tiles) *before downloading*
3. [Start downloading that region](/v9/bulk-downloading/start)\
   *↳ Optionally, when testing,* [*try the miniature tile server*](/v9/bulk-downloading/testing-tile-server)
4. [Listen for progress](/v9/bulk-downloading/start#listen-for-progress) events to update your user
5. Optionally, [control (pause/resume/cancel) the download](/v9/bulk-downloading/control)


# Create A Region

Regions (`BaseRegion`s) are geographical areas that do not yet have any of the necessary extra information to start a download (this is the responsibility of`DownloadableRegion`).

There are 4 types of `BaseRegion`:

{% tabs %}
{% tab title="Rectangle" %}
`RectangleRegion`s are defined by a `LatLngBounds`: two opposite `LatLng`s.

```dart
final region = RectangleRegion(
    LatLngBounds(LatLng(0, 0), LatLng(1, 1)),
);
```

{% hint style="info" %}
This is usually all you get from most apps, so why not give your user a unique experience with some of our other region types...
{% endhint %}
{% endtab %}

{% tab title="Circle" %}
`CircleRegion`s are defined by a center `LatLng` and radius *in kilometers*.

```dart
final region = CircleRegion(
    LatLng(0, 0), // Center coordinate
    1, // Radius in kilometers
);
```

If you instead have two coordinates, one in the center, and one on the edge, you can use ['latlong2's `Distance.distance()`](https://pub.dev/documentation/latlong2/latest/latlong2/Distance/distance.html) method, as below:

```dart
final centerCoordinate = LatLng(0, 0); // Center coordinate
final region = CircleRegion(
    centerCoordinate,
    const Distance(roundResult: false).distance(
        centerCoordinate,
        LatLng(1, 1), // Edge coordinate
    ) / 1000; // Convert to kilometers
);
```

{% endtab %}

{% tab title="(Poly)Line" %}
`LineRegion`s are defined by a list of `LatLng`s, and a radius in meters.

This could be used to download tiles along a planned travel route, for example hiking or long-distance driving. Import coordinates from a routing engine, or from a GPX/KML file for maximum integration!

```dart
final region = LineRegion(
    [LatLng(0, 0), LatLng(1, 1), ...], // List of coordinates
    1000, // Radius in meters
);
```

{% hint style="warning" %}
This region may generate more tiles than strictly necessary to cover the specified region. This is due to an internal limitation with the region generation algorithm, which uses rectangles to approximate the actual desired shape.
{% endhint %}

{% hint style="warning" %}
This type of region may consume more memory/RAM when generating tiles than other region types.
{% endhint %}
{% endtab %}

{% tab title="Custom Polygon" %}
`CustomPolygonRegion`s are defined by a list of `LatLng`s defining the outline of a [simple polygon](https://en.wikipedia.org/wiki/Simple_polygon).

```dart
final region = CustomPolygonRegion(
    [LatLng(0, 0), LatLng(1, 1), ...], // List of coordinates
);
```

{% hint style="warning" %}
Polygons should not contain self-intersections. These may produce unexpected results.

Holes are not supported.
{% endhint %}
{% endtab %}

{% tab title="Recoverable" %}
`RecoverableRegion`s aren't technically the same type of region as the others, and is the odd one out.

However, it can be converted to a downloadable region in the same way as the others, or the original `BaseRegion` extracted using `toRegion`.

For more info, see [Recovery](/v9/stores-and-roots/roots/recovery).
{% endtab %}
{% endtabs %}


# Prepare For Downloading

[`BaseRegion`s](/v9/bulk-downloading/regions) must be converted to `DownloadableRegion`s before they can be used to download tiles.

These contain the original `BaseRegion`, but also some other information necessary for downloading, such as zoom levels and URL templates.

```dart
final downloadableRegion = region.toDownloadable(
    minZoom: 1,
    maxZoom: 18,
    options: TileLayer(
        urlTemplate: '<your tile server>',
        userAgentPackageName: 'com.example.app',
    ),
),
```

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/BaseRegion/toDownloadable.html>" %}

{% hint style="info" %}
The `TileLayer` passed to the `options` parameter must include both a `urlTemplate` (or WMS configuration) and a `userAgentPackageName`, unless it is only being used to [`check` the number of tiles in the region](#checking-number-of-tiles).
{% endhint %}

## Checking Number Of Tiles

Before continuing to downloading the region, you can use `check()` to count the number of tiles it will attempt to download. This is accessible through `FMTCStore().download`.

The method takes the `DownloadableRegion` generated above, and will return an `int` number of tiles. For larger regions, this may take a few seconds.

{% hint style="warning" %}
This figure will not take into account any skipped sea tiles or skipped existing tiles, as those are handled at the time of download.
{% endhint %}


# Start Download

Now that you have constructed a `DownloadableRegion`, you're almost ready to go.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreDownload/startForeground.html>" %}

## Customization Options

Before you call `startForeground` (via `FMTCStore().download`) to start the download, check out the customization parameters:

* `parallelThreads` (defaults to 5)\
  The number of simultaneous download threads to run
* `maxBufferLength` (defaults to 200)\
  The number of tiles to temporarily persist in memory before writing to the cache
* `skipExistingTiles` (defaults to `false`)\
  Whether to avoid re-downloading tiles that have already been cached
* `skipSeaTiles` (defaults to `true`)\
  Whether to avoid caching tiles that are entirely sea (based on whether they have the same pixels as the tile at z17, x0, y0, which is assumed to be sea)
* `rateLimit`\
  The maximum number of tiles that can be attempted per second
* `maxReportInterval` (defaults to 1 second)\
  The duration in which to emit *at least* one `DownloadProgress` event
* `disableRecovery` (defaults to `false`)\
  Whether to avoid registering this download with the [Recovery](/v9/stores-and-roots/roots/recovery) system for safe recovery if the download fails

{% hint style="warning" %}
Ensure `skipSeaTiles` is disabled when downloading from a server where the tile at z17, x0, y0 is not a consitently colored sea tile, or where different sea tiles look different, such as with satellite imagery. FMTC cannot yet skip sea tiles that match these conditions.
{% endhint %}

{% hint style="info" %}
The [Recovery](/v9/stores-and-roots/roots/recovery) system can slow a download, as it must be regularly updated with the latest progress of the download, and this data is not currently batched (so it occurs for every downloaded tile). This is an optimization planned for later implementation.

Therefore, where speed is significant and the download is unlikely to be unexpectedly interrupted, consider disabling download recovery.
{% endhint %}

## Start Download

{% hint style="warning" %}
Before using FMTC, especially to bulk download or import/export, ensure you comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

For example, OpenStreetMap's tile server forbids bulk downloading: <https://operations.osmfoundation.org/policies/tiles>. And Mapbox has restrictions on importing/exporting from outside of the user's own device.

For testing purposes, check out the testing tile server included in the FMTC project: [Testing Tile Server](/v9/bulk-downloading/testing-tile-server).
{% endhint %}

```dart
final progressStream = FMTCStore('storeName').download.startForeground(
    region: downloadableRegion,
    // other options...
);
```

{% hint style="info" %}
Whilst not recommended, it is possible to start and control multiple downloads simultaneously, by using a unique `Object` as the `instanceId` argument. This 'key' can then later be used to control its respective download instance.

Note that this option may be unstable.
{% endhint %}

## Listen For Progress

The `startForeground` method returns a (non-broadcast) `Stream` of `DownloadProgress` events, which contain information about the overall progress of the download, as well as the state of the latest tile attempt.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/DownloadProgress-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/TileEvent-class.html>" %}

To reflect the information from a single event back to the user, use a `StreamBuilder`, and build the UI dependent on the 'snapshots' of the stream. If you need to keep track of information from across multiple events, see [#keeping-track-across-events](#keeping-track-across-events "mention") below.

### Keeping Track Across Events

In addition to display each individual event to your user, you may also need to keep track of information from across multiple `DownloadProgress` events. In this case, you'll likely need to use the `latestTileEvent` getter to access the latest `TileEvent` object, and keep track of its properties.

For example, you may wish to keep a list of all the failed tiles' URLs.

However, there are 3 important things to keep in mind when doing this:

<details>

<summary>Memory Consumption</summary>

Avoid keeping a list of *all* emitted events. Instead, keep a 'circular buffer' of the useful subset of events.

A single download can have many events, and storing them all will consume a lot of memory. It is easy to consume all of the remaining allocated memory, and crash the app.

</details>

<details>

<summary>Data Loss</summary>

Avoid keeping track of required information internally through a `StreamBuilder` intended to display a UI.

A `StreamBuilder` will not necessarily call the `builder` callback once per event, especially if the download has a high TPS. Therefore, events may be lost.

</details>

<details>

<summary>Data Duplication</summary>

Avoid keeping track of events where the `latestTileEvent.isRepeat` property is `true`.

These `TileEvents` are exact repeats of the previous event, usually due to the `maxReportInterval` functionality. Therefore, including both in a dataset would be erroneous.

</details>


# Control Downloads

There are 3 methods available to control a download, and 1 method to check the download's current state.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreDownload-class.html>" %}

{% hint style="info" %}
It may take some time to perform these operations if the download has a low TPS, as each thread checks the current state/awaits a signal between each of their tiles.

`pause` & `cancel` are asynchronous for this reason, and will complete when all threads are paused or cancelled. `resume` returns immediately.
{% endhint %}

## Pause/Resume

If your user needs to temporarily pause the download, with the ability to resume it at some point later (within the same app session), use `pause` and `resume`.

`pause` allows all threads to finish the tile they're currently downloading, then forces them to wait for a signal sent by `resume` before they can download their next tile. It does not write any buffered tiles to the store.

Use `isPaused` to check whether the download is currently paused.

## Cancel

If your user needs to stop the download entirely, use `cancel`.

`cancel` allows all threads to finish the tile they're currently downloading, then forces them to cleanup and 'kill' themselves. Any buffered tiles are written to the store before the future returned by `cancel` is completed.

It is safe to use `cancel` after `pause` without `resume`ing first.


# Testing Tile Server

A miniature tile server, intended to test and calibrate FMTC, has been included in the project.

{% hint style="success" %}
Avoid making too many costly and slow requests to your chosen tile server during development by using this miniature tile server!
{% endhint %}

For internal testing and development purposes, it also doubles down as a handy way to test your application without making too many costly and slow requests to your chosen tile server. When in use with the example application, it can handle over 2000 tiles/second.

It is a very simple web HTTP server written in Dart, that responds to all\* requests with a tile. There is a theoretically 90% chance that this tile will be a specific land tile, and a 10% chance that it will be a sea tile - designed to test the sea tile skipping functionality. *There are only these two tiles - it is not a full tile server.*

<div><figure><img src="/files/0UOPPp6ilSV4hB74oT8u" alt="" width="128"><figcaption><p><em>the</em> Land Tile<br>90% chance</p></figcaption></figure> <figure><img src="/files/AIdxfAVSp3u7lE9FwoT2" alt="" width="128"><figcaption><p><em>the</em> Sea Tile<br>10% chance</p></figcaption></figure></div>

To use this tile server:

{% hint style="info" %}
The tile server is hardcoded to use standard HTTP port 7070 to serve content, which is usually free. Other programs must not be using this port.
{% endhint %}

1. Download/compile & start the tile server (no permanent installation required)
   * On Windows or Linux\
     Download a copy of the latest '\<platform>-ts' artifact from GitHub Actions, and run the executable inside: <https://nightly.link/JaffaKetchup/flutter_map_tile_caching/workflows/main/main>
   * On other platforms\
     Clone the [FMTC GitHub repository](https://github.com/JaffaKetchup/flutter_map_tile_caching/) to your device, then run '/tile\_server/bin/tile\_server.dart' manually
2. Use the following URL to connect to it
   * From the local device: `http://localhost:7070/{z}/{x}/{y}.png`
   * From the same network (on another device): `http://<your-local-ip>:7070/{z}/{x}/{y}.png`\
     To find your local IP address, follow the [instructions for your OS here](https://www.avast.com/c-how-to-find-ip-address)
3. Control the tile server using keyboard key presses in the console window
   * `q`: Release port 7070 and quit the executable
   * UP arrow: Increase the artificial delay between request and response by 2ms
   * DOWN arrow: Decrease the artificial delay between request and response by 2ms


# Introduction

{% hint style="warning" %}
Before using FMTC, especially to bulk download or import/export, ensure you comply with the appropriate restrictions and terms of service set by your tile server. Failure to do so may lead to any punishment, at the tile server's discretion.

This library and/or the creator(s) are not responsible for any violations you make using this package.

For example, OpenStreetMap's tile server forbids bulk downloading: <https://operations.osmfoundation.org/policies/tiles>. And Mapbox has restrictions on importing/exporting from outside of the user's own device.

For testing purposes, check out the testing tile server included in the FMTC project: [Testing Tile Server](/v9/bulk-downloading/testing-tile-server).
{% endhint %}

FMTC allows stores (including all necessary tiles and metadata) to be [exported](/v9/external/exporting) to an 'archive'/a standalone file, then [imported](/v9/external/importing) on the same or a different device!

{% hint style="info" %}
FMTC does not support exporting tiles to a raw Z/X/Y directory structure that can be read by other programs.
{% endhint %}

For example, this can be used to create backup systems to allow user's to store maps for later off-device, sharing/distribution systems, or to distribute a preset package of tiles to all users without worrying about managing IO or managing assets, and still allowing users to update their cache afterward!

***

External functionality is accessed via `FMTCRoot.external('~/path/to/file.fmtc')`.

All functionalities require the path to an archive file. The file may not necessarily exist: for the `export` method only, the file will be created if it does not exist, and overwritten if it does. Other methods require the file to exist, and be in a valid format.

{% hint style="warning" %}
Archives are backend specific. They cannot necessarily be imported by a backend different to the one that exported it.
{% endhint %}


# Exporting

The `export()` method copies the stores, along with all necessary tiles, to a seperate archive at the specified location (creating it if non-existent, overwriting it otherwise), in the FMTC (.fmtc) format.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootExternal/export.html>" %}

```dart
await FMTCRoot.external('~/path/to/file.fmtc').export(['storeName']);
```


# Importing

The `import()` method copies the specified archive to a temporary location, then opens it and extracts the specified stores (or all stores if none are specified) & all necessary tiles, merging them into the in-use database. The specified archive must exist, must be valid, and should contain all the specified stores, if applicable.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootExternal/import.html>" %}

There is no support for directly overwriting the in-use database with the archived database, but this may be performed manually while FMTC is uninitialised.

{% hint style="warning" %}
There must be enough storage space available on the device to duplicate the entire archive, and to potentially grow the in-use database.

This is done to preserve the original archive, as this operation writes to the temporary archive. The temporary archive is deleted after the import has completed.
{% endhint %}

```dart
final importResult =
    await FMTCRoot.external('~/path/to/file.fmtc').import(['storeName']);
```

The returned value is complex. See the API documentation for more details:

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/ImportResult.html>" %}

## Conflict Resolution Strategies

If an importing store has the same name as an existing store, a conflict has occurred, because stores must have unique names. FMTC provides 4 resolution strategies:

* `skip`\
  Skips importing the store
* `replace`\
  Deletes the existing store, replacing it entirely with the importing store
* `rename`\
  Appends the current date and time to the name of the importing store, to make it unique
* `merge`\
  Merges the two stores' tiles and metadata together

In any case, a conflict between tiles will result in the newer (most recently modified) tile winning (it is assumed it is more up-to-date).

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/ImportConflictStrategy.html>" %}

## List Stores

If the user must be given a choice as to which stores to import (or it is helpful to know), and it is unknown what the stores within the archive are, the `listStores` getter will list the available store names without performing an import.

{% hint style="warning" %}
The same storage pitfalls as `import` exist. `listStores` must also duplicate the entire archive.
{% endhint %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootExternal/listStores.html>" %}


# flutter\_map\_tile\_caching

A plugin for 'flutter\_map' providing advanced offline functionality

{% hint style="danger" %}
You're viewing documentation for an older version of FMTC (v8).

For the latest documentation, see [v9](https://fmtc.jaffaketchup.dev/v9/).
{% endhint %}

{% embed url="<https://github.com/stars/JaffaKetchup/lists/fmtc-modules>" %}

<table data-card-size="large" data-view="cards" data-full-width="false"><thead><tr><th></th><th></th><th></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><p><mark style="color:blue;">◉</mark> 📲</p><p><strong>Caching × Downloading</strong></p></td><td>Get both <strong>dynamic caching</strong> that works automatically as the user browses the map, and <strong>bulk downloading</strong> to preload regions onto the user's device, all in one convenient package!</td><td><ul><li><a data-footnote-ref href="#user-content-fn-1">Multi-cache ('store') support</a></li><li><a data-footnote-ref href="#user-content-fn-2">Wide variety of 'region' shapes</a></li><li><a data-footnote-ref href="#user-content-fn-3">Automatic sea tile skipping</a></li></ul></td><td></td></tr><tr><td><p><mark style="color:red;">◉</mark> 🏃</p><p><strong>Ultra-fast &#x26; Performant</strong></p></td><td>No need to bore your users to death anymore! Bulk downloading is super-fast, and can even reach speeds of <strong>over 600 tiles per second</strong><a data-footnote-ref href="#user-content-fn-4">*</a>. Existing cached tiles can be displayed on the map <strong>almost instantly</strong>. Don't even mention memory consumption: you won't realise there is any.</td><td><ul><li>Multi-threaded downloads</li><li>Tile buffering to reduce database writes</li><li>Streamlined behind-the-scenes to reduce memory consumption</li></ul></td><td></td></tr><tr><td><p><mark style="color:green;">◉</mark> 🧩</p><p><strong>Import &#x26; Export</strong></p></td><td>Using one of our <a href="https://github.com/stars/JaffaKetchup/lists/fmtc-modules">official extension modules</a>, allow your users to <strong>share</strong><a data-footnote-ref href="#user-content-fn-5">*</a> <strong>and backup</strong> their cached tiles! You could even remote control your organization's devices, by pushing tiles to them, keeping your tile requests (&#x26; costs) low!</td><td></td><td></td></tr><tr><td><p><mark style="color:purple;">◉</mark> 💖</p><p><strong>Quick To Implement (&#x26; Quicker To Love)</strong></p></td><td>A basic caching implementation can be setup in four quick steps, and shouldn't even take 5 minutes to set-up. Check out our <a data-mention href="/pages/ZLHYkAJpLD5h8yB2SNyt">/pages/ZLHYkAJpLD5h8yB2SNyt</a> instructions.</td><td>When you've done this, you'll realise how great your app is with FMTC <span data-gb-custom-inline data-tag="emoji" data-code="1f604">😄</span></td><td></td></tr></tbody></table>

{% hint style="info" %}
FMTC currently has some stability issues on certain platforms, especially iOS. These stability issues can cause unexpected behaviours.

I am working to resolve these issues in v9, benefiting from the improved stability of Isar v4.

For more information about what this means, see [Known Issues](/v8/known-issues#isar-stability-issues).
{% endhint %}

***

## Supporting Me

I work on all of my projects in my spare time, including maintaining (along with a team) Flutter's № 1 (non-commercially maintained) mapping library 'flutter\_map', bringing it back from the brink of abandonment, as well as my own plugin for it ('flutter\_map\_tile\_caching') that extends it with advanced caching and downloading.\
Additionally, I also own the Dart encoder/decoder for the QOI image format ('dqoi') - and I am slowly working on 'flutter\_osrm', a wrapper for the Open Source Routing Machine.

Sponsorships & donations allow me to continue my projects and upgrade my hardware/setup, as well as allowing me to have some starting amount for further education and such-like.\
And of course, a small amount will find its way into my Jaffa Cakes fund (<https://en.wikipedia.org/wiki/Jaffa_Cakes>) - why do you think my username has "Jaffa" in it?

Many thanks for any amount you can spare, it means a lot to me!

{% embed url="<https://github.com/sponsors/JaffaKetchup>" %}

## (Proprietary) Licensing

*I am not a lawyer, and this information is to the best of my understanding. You are urged to read the license yourself for a thorough understanding.*

This project is released under GPL v3. For detailed information about this license, see <https://www.gnu.org/licenses/gpl-3.0.en.html>. [choosealicense.com](https://choosealicense.com/licenses/gpl-3.0/) summarises the license with the following paragraph:

> Permissions of this strong copyleft license are conditioned on **making available complete source code of licensed works and modifications, which include larger works using a licensed work, under the same license**. Copyright and license notices must be preserved. Contributors provide an express grant of patent rights.

Essentially, whilst you can use this code within commercial projects, they must not be proprietary - they incorporate this 'licensed work' so they must be available under the same license. You must distribute your source code on request (under the same GPL v3 license) to anyone who uses your program.

However, I am willing to sell custom alternative proprietary licenses on a case-by-case basis and on request.

I learnt (and am still learning) to code with free, open-source software due to my age and lack of money, and for that reason, I believe in promoting open-source wherever possible to give equal opportunities to everybody, no matter their age or financial position. I'm not sure it's fair for commercial proprietary applications to use software made by people for free out of generosity. On the other hand, I am also trying to make a small amount of money from my projects, by donations or by selling licenses. And I recognise that commercial businesses may want to use my projects for their own proprietary applications.

Therefore, if you would like a license to use this software within a proprietary, I am willing to sell a (preferably yearly or usage based) license for a reasonable price. If this seems like what you want/need, please do not hesitate to get in touch at <fmtc@jaffaketchup.dev>.

## Get Help

Not quite sure about something? No problem. Please get in touch via any of these methods, and I'll be with you as soon as possible:

* For bug reports & feature requests: [GitHub Issues](https://github.com/JaffaKetchup/flutter_map_tile_caching/issues)
* For implementation/general support: The *#plugin* channel on the [flutter\_map Discord server](https://github.com/fleaflet/flutter_map#discord-server)
* For other inquires: <fmtc@jaffaketchup.dev>

[^1]: Keep your users' tiles organized, and even let them control what goes where!

[^2]: Choose from rectangular, circular, and line-based region shapes to bulk download tiles from. Allow your users to download their travel route without unnecessary tiles!

[^3]: Avoid downloading redundant, waste-of-space tiles that cover oceans, with this unique functionality, and bless your users with the gift of more usable capacity for useful maps!

[^4]: Speed is very dependent on tile server ability.

    Some tile servers will impose limits on bulk downloading (speeds and frequencies). Always read their ToS before using FMTC.

[^5]: Some tile servers forbid sharing of their tiles. Always read their ToS before using FMTC.


# Is FMTC Right For Me?

**In a one word answer: Yes.**

FMTC aims to provide all the functionality you will need for caching, in a way that requires little knowledge of the internals of 'flutter\_map' and other caching fundamentals, and little effort from you (for most setups).

However, there are two main concerns that apply to most people:

* Can you abide by the GPL v3 license, or do you need an alternative proprietary license?\
  *See* [flutter\_map\_tile\_caching](/v8#proprietary-licensing) *for more information about this*
* Do you require all the functionality and control that FMTC offers?

Answering the second question is the more difficult than answering the first one, but it will tell you whether it's worth your while getting an alternative proprietary license if you need it.

\---

You'll want to consider using FMTC over a custom DIY solution if you need any of the features below. These take a long time to reproduce and get right, but we've already done the hard work for you!

* You need to provide bulk downloading or import/export functionality to your users
* You or your users need a lot of fine-grain control over the cached tiles

However, if you match all of the following, you may find that a DIY/custom solution will work better for you.

* You are developing a proprietary application, and we can't reach an agreement for an alternative license\
  *I aim to agree a price or deal that works for the both of us, so please do get in touch even if you're unsure if you can afford a license*
* You need only very basic caching
* You need only browse caching

If you're still not sure, please get in touch: [flutter\_map\_tile\_caching](/v8#get-help). I'm always happy to offer guidance :)


# Quickstart

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing a proprietary (non open-source) application, this affects you and your application's legal right to distribution. For more information, please see [flutter\_map\_tile\_caching](/v8#proprietary-licensing).
{% endhint %}

{% hint style="warning" %}
Before using FMTC, ensure you comply with the appropriate rules and ToS set by your tile server. Failure to do so may lead to a permenant ban, or any other punishment.

This library and/or the creator(s) are not responsible for any violations you make using this package.

OpenStreetMap's can be [found here](https://operations.osmfoundation.org/policies/tiles): specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
{% endhint %}

This page guides you through a simple, fast setup of FMTC that just enables basic browse caching, without any of the bells and whistles that you can discover throughout the rest of this documentation.

## 1. [Install](/v8/get-started/installation)

Depend on the latest version of the package from pub.dev, then import it into the appropriate files of your project.

{% code title="Console/Terminal" %}

```sh
flutter pub add flutter_map_tile_caching
```

{% endcode %}

```dart
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
```

## 2. [Initialise](/v8/usage/initialisation)

Perform the startup procedure to allow usage of FMTC's APIs and connect to the underlying systems.

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">import 'package:flutter/widgets.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

Future&#x3C;void> main() async {
    WidgetsFlutterBinding.ensureInitialized();   
<strong>    await FlutterMapTileCaching.initialise();
</strong>    // ...
    // runApp(MyApp());
}
</code></pre>

## 3. [Create a store](/v8/usage/roots-and-stores#without-automatic-creation)

Create an isolated space to store tiles and other information to be accessed by the map and other methods.

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">Future&#x3C;void> main() async {
    WidgetsFlutterBinding.ensureInitialized();   
    await FlutterMapTileCaching.initialise();
<strong>    await FMTC.instance('mapStore').manage.createAsync();
</strong>    // ...
    // runApp(MyApp());
}
</code></pre>

## 4. [Connect to 'flutter\_map'](/v8/usage/integration)

Enable your `FlutterMap` widget to use the caching and underlying systems of FMTC.

<pre class="language-dart"><code class="lang-dart">import 'package:flutter_map/flutter_map.dart';

TileLayer(
    urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
    userAgentPackageName: 'com.example.app',
<strong>    tileProvider: FMTC.instance('mapStore').getTileProvider(),
</strong>    // Other parameters as normal
),
</code></pre>

{% hint style="success" %}
You should now have a basic working implementation of FMTC that caches tiles for you as you browse the map!

There's a lot more to discover, from management to bulk downloading, and from statistics to exporting/importing.
{% endhint %}


# Installation

{% hint style="success" %}
Looking to start using FMTC in your project? Check out the [Quickstart](/v8/get-started/quickstart) guide!
{% endhint %}

{% hint style="warning" %}
FMTC is currently somewhat unstable for applications with a wide public reach, due to some issues with the Isar dependency.

v8 is much more stable than v7.

For more information about what this means, see [Known Issues](/v8/known-issues#isar-stability-issues).
{% endhint %}

## Depend On

### From [pub.dev](https://pub.dev/packages/flutter_map_tile_caching)

This is the recommended method of installing this package as it ensures you only receive the latest stable versions, and you can be sure pub.dev is reliable.

Just import the package as you would normally, from the command line:

<pre class="language-shell"><code class="lang-shell"><strong>flutter pub add flutter_map_tile_caching
</strong>flutter pub add fmtc_plus_background_downloading # OPTIONAL
flutter pub add fmtc_plus_sharing # OPTIONAL
</code></pre>

### From [github.com](https://github.com/JaffaKetchup/flutter_map_tile_caching)

If you urgently need the latest version, a specific branch, or a specific fork, you can use this method.

{% hint style="info" %}
Commits available from Git (GitHub) may not be stable. Only use this method if you have no other choice.
{% endhint %}

Add the following lines to your pubspec.yaml file under the 'dependencies\_override' section:

<pre class="language-yaml" data-title="pubspec.yaml"><code class="lang-yaml">dependency_overrides:
<strong>    flutter_map_tile_caching:
</strong><strong>        git:
</strong><strong>            url: https://github.com/JaffaKetchup/flutter_map_tile_caching.git
</strong>    fmtc_plus_background_downloading: # OPTIONAL
        git:
            url: https://github.com/JaffaKetchup/fmtc_plus_background_downloading.git
    fmtc_plus_sharing: # OPTIONAL
        git:
            url: https://github.com/JaffaKetchup/fmtc_plus_sharing.git
</code></pre>

## Import

After installing the package, import it into the necessary files in your project:

<pre class="language-dart"><code class="lang-dart"><strong>import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
</strong>import 'package:fmtc_plus_background_downloading/fmtc_plus_background_downloading.dart'; // OPTIONAL
import 'package:fmtc_plus_sharing/fmtc_plus_sharing.dart'; // OPTIONAL
</code></pre>


# Additional Setup

## `flutter_map` Installation & Setup

You must make sure you follow `flutter_map`'s installation **and** additional setup instructions.

{% embed url="<https://docs.fleaflet.dev/getting-started/installation>" %}

## [`fmtc_plus_background_downloading`](https://github.com/JaffaKetchup/fmtc_plus_background_downloading) Installation & Setup

{% hint style="warning" %}
This module is only supported on Android.
{% endhint %}

To install this module, follow the [Installation](/v8/get-started/installation) instructions for this package.

### Background Processes

Add the following to 'android\app\src\main\AndroidManifest.xml' and any other manifests:

<pre class="language-diff" data-title="AndroidManifest.xml"><code class="lang-diff"> &#x3C;manifest xmlns:android="http://schemas.android.com/apk/res/android" package="packageName">
+    &#x3C;uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+    &#x3C;uses-permission android:name="android.permission.WAKE_LOCK" />
+    &#x3C;uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<strong> &#x3C;application android:label="appName" android:icon="appIcon">
</strong></code></pre>

This will allow the application to acquire the necessary permissions (should the user allow them at runtime) to a background process.

* `FOREGROUND_SERVICE`: allows the application to start a foreground service - a type of Android service that can run in the background, as long as the application isn't force stopped.
* `WAKE_LOCK`: allows the background process (technically foreground service) to run even when the device is locked/asleep. Also allows the acquisition of a WiFi lock.
* `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` (must be requested at runtime): assists with the background process not being killed by the system.

### Notification Support

Background downloading needs to show notifications, which requires a 3rd party package. See it's installation/setup instructions:

{% embed url="<https://pub.dev/packages/flutter_local_notifications#-android-setup>" %}

## [`fmtc_plus_sharing`](https://github.com/JaffaKetchup/fmtc_plus_sharing) Installation & Setup

To install this module, follow the [Installation](/v8/get-started/installation) instructions for this package.

### Android (11+)

Please follow these additional instructions for supporting Android versions above 11 and building for release:

{% embed url="<https://github.com/miguelpruivo/flutter_file_picker/wiki/Setup#android>" %}

### iOS

{% hint style="info" %}
Unfortunately, I do not have the hardware to test this library on Apple platforms. If you find issues, please report them!
{% endhint %}

*It is unknown whether this setup is needed in all cases, so it is recommended to follow these only when you receive errors during building your app.*

Some developers may have issues when releasing the app or uploading to TestFlight - see [issue #69](https://github.com/JaffaKetchup/flutter_map_tile_caching/issues/69) for the first report of this problem. This is due to some of this library's dependencies on platform plugins.

* Annotate that access is not needed to the Media, Audio, and Documents directories - this package uses only custom file types. Add these lines to your Podfile just before `target 'Runner' do`.

  <pre data-title="Podfile"><code>Pod::PICKER_MEDIA = false
  Pod::PICKER_AUDIO = false
  Pod::PICKER_DOCUMENT = false
  </code></pre>
* Add `UIBackgroundModes` capability with the `fetch` and `remote-notifications` keys to Xcode, to describe why your app needs to access background tasks - in this case to bulk download maps in the background.


# Example Application

This package contains a full example application - prebuilt for Android and Windows - showcasing the most important features of this package and it's modules.

{% hint style="info" %}
The example app isn't intended for beginners or as a starting point for a project. It is intended for evaluation purposes, to discover FMTC's capabilities, and how it might be implemented into an app.

To start using FMTC in your own app, please check out the [Quickstart](/v8/get-started/quickstart) guide instead.
{% endhint %}

## Prebuilt Example Applications

There are prebuilt applications for Android and Windows available on GitHub.

These are automatically built (using GitHub Actions) from the latest available commits every time the source files change, so may include functionality not yet available via pub.dev installation.

{% hint style="success" %}
You can verify that these applications are built directly from the source code, and have not been maliciously modified to included malware, because the committer is always the GitHub Actions Bot, which can be verified by the profile icon linking to a non-profile page.
{% endhint %}

{% embed url="<https://github.com/JaffaKetchup/flutter_map_tile_caching/tree/main/prebuiltExampleApplications>" %}

### Android

To run the prebuilt Android application on most devices, download the .apk package (from [#prebuilt-example-applications](#prebuilt-example-applications "mention")) to your device, then execute it to install it.

After installation, it will appear in the launcher like any other application.

{% hint style="info" %}
The operating system may request permissions to install applications from unknown sources: you must allow this.
{% endhint %}

### Windows

To run the prebuilt Windows application on most devices, download the .exe package (from [#prebuilt-example-applications](#prebuilt-example-applications "mention")) to your device, then execute it to install it.

It will require a very simple installation with no administrator privileges required, then it will appear in the Start Menu and search bars like any other application. You can optionally choose to create a desktop shortcut.

{% hint style="info" %}
You may receive security warnings depending on your system setup: these are false positives and occur due to the package being unsigned.
{% endhint %}

### Other Platforms

For other platforms, there are no prebuilt applications.

You'll have to [clone this project](https://github.com/JaffaKetchup/flutter_map_tile_caching.git), open the 'example' directory, and then build for your desired platform using Dart and Flutter as normal.


# Initialisation

{% hint style="warning" %}
**FMTC is licensed under GPL-v3.**

If you're developing a proprietary (non open-source) application, this affects you and your application's legal right to distribution. For more information, please see [flutter\_map\_tile\_caching](/v8#proprietary-licensing).
{% endhint %}

{% hint style="warning" %}
Before using FMTC, ensure you comply with the appropriate rules and ToS set by your tile server. Failure to do so may lead to a permenant ban, or any other punishment.

This library and/or the creator(s) are not responsible for any violations you make using this package.

OpenStreetMap's can be [found here](https://operations.osmfoundation.org/policies/tiles): specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
{% endhint %}

The main basis of this package is the `FlutterMapTileCaching` object, which exposes the majority of FMTC's APIs (through `FMTC.instance`), and also contains most of the state needed to connect and communicate with the underlying systems.

Therefore, it must be asynchronously initialised on every app startup, usually in the `main` method that runs directly after the Flutter environment starts.

{% hint style="success" %}
`FMTC` is a shorthand type alias for `FlutterMapTileCaching`, and works in exactly the same way. Often, documentation will use the shortened version to save space, and you should do so in your code as well.
{% endhint %}

{% hint style="danger" %}
You must call `initialise()` before trying to use `instance.`Failure to do so will throw a `StateError`.
{% endhint %}

<pre class="language-dart" data-title="main.dart"><code class="lang-dart">import 'package:flutter/widgets.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

Future&#x3C;void> main() async {
    <a data-footnote-ref href="#user-content-fn-1">WidgetsFlutterBinding.ensureInitialized();</a>   
    
<strong>    await FlutterMapTileCaching.initialise();
</strong><strong>    // FMTC.instance;
</strong>    
    // Run your app and do all of that other stuff
}
</code></pre>

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/FlutterMapTileCaching/initialise.html>" %}

## Initialisation Safety

{% hint style="info" %}
In summary, in the extremely unlikely event of database corruption, FMTC can recover automatically (in most circumstances), with minimal data loss, given enough fatal app crashes - corruption cannot be caught in any other way.
{% endhint %}

Whilst it is (almost) impossible for one of the underlying [Isar](https://isar.dev/) databases to become corrupted during normal usage, or even due to a bug, it can happen if the database file is modified manually.

In this case, it is currently impossible to avoid a fatal crash during initialisation, as the database reader/parser crashes all Dart threads & isolates without warning. Please see this issue I opened:

{% embed url="<https://github.com/isar/isar/issues/1011>" %}

However, FMTC has a workaround.

By using a basic temporary text file, the IDs/filenames of successfully opened databases can be recorded. Once all databases have been opened, the file is deleted, meaning that the safety system will not intervene on the next app launch. However, if a database open attempt crashes the app, the file will not be deleted, and it will contain the list of safe databases.

Because the order in which databases are opened is determinate (alphabetical), the app can open every database one by one, (metaphorically) crossing it off the list. When there are no more safe databases, but more database files, the next database is deleted instead of being opened. The initialisation then continues as normal, still using the file appropriatley.

Therefore, one fatal crash is enough to detect one faulty database, and delete it, allowing the app to initialise normally on the next launch.

If there are multiple corrupted databases (`n`), one fatal crash is needed per database, so the app will successfully open after `n + 1`.

All of this means that FMTC can usually recover from a database corruption with minimal data loss. However, if the database filenames/IDs are also changed, the behaviour is unspecified, especially if the initialisation file already exists.

[^1]: A Flutter method required to prevent a fatal error from occurring on app launch.


# Using Roots & Stores

## How It Works

FMTC uses Roots and Stores to structure it's data. Previously, these represented actual directories (hence the reference to directories within the codebase), but now they just represent [Isar](https://isar.dev/) databases.

There is usually only one root (formed of a directory and some miscellaneous databases) per application, which contains multiple stores (formed of a single database holding a descriptor, multiple tiles, and [metadata](/v8/usage/roots-and-stores/metadata)).

## Chaining

Once you can access `FlutterMapTileCaching.instance` after [Initialisation](/v8/usage/initialisation), chaining of methods and accessors is used to access functionality.

1. Base Chains are used as an intermediate step to access functionality on Roots and Stores
2. Additional Chains are added to Base Chains to reach the actual functionality.

### Base Chains

To get the Root, chain on `rootDirectory` (the name of the accessor is a remnant leftover from previous versions).

```dart
FlutterMapTileCaching.instance.rootDirectory;
```

To get a Store, there are two possible methods. FMTC does not use code generation, so store names are flexible, and so use `String`s to access the Store.

{% tabs %}
{% tab title="Without Automatic Creation" %}
{% hint style="success" %}
This is the recommended method. Always use this method where possible.
{% endhint %}

`call()`/`()` gets a `StoreDirectory` by the name inside the parentheses.

Note that the store will not be automatically created once accessed, as this requires asynchronous tasks, so it is important to create the store manually (if necessary).

<pre class="language-dart"><code class="lang-dart"><strong>final store = FlutterMapTileCaching.instance('storeName');
</strong>await store.manage.create(); // Create the store if necessary
</code></pre>

{% hint style="info" %}
Examples in this documentation will usually assume that the stores are already created/ready.
{% endhint %}
{% endtab %}

{% tab title="With Automatic Synchronous Creation" %}
{% hint style="warning" %}
This method is not recommended, for the reasons listed below. Prefer using [#without-automatic-creation](#without-automatic-creation "mention") wherever possible.

* It is synchronous and therefore blocks the main thread
* It results in hard to trace code, as the creation calls are no longer obvious
* It encourages minimizing accesses to increase performance, which is against the philosophy of the chaining strategy
  {% endhint %}

`[]` gets a `StoreDirectory` by the name inside the parenthesis.

Note that the store will be automatically created once accessed, although it will be done synchronously in a way that blocks the main thread.

```dart
final store = FlutterMapTileCaching.instance['storeName'];
```

{% endtab %}
{% endtabs %}

### Additional Chains

After this, you can chain any of the following members/accessors (each will be accessible on a Root, a Store, or both).

{% hint style="success" %}
Prefer using asynchronous versions of sub-methods where possible, as these won't block the UI thread.

If running inside an isolate, or blocking the UI thread doesn't matter, use the synchronous versions wherever possible, as they have slightly better performance.
{% endhint %}

{% content-ref url="/pages/qr1Ja34BhspEihyVfR5I" %}
[flutter\_map Integration](/v8/usage/integration)
{% endcontent-ref %}

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th><select multiple><option value="ca10bc578ce042d1a07c85ac6ca0125e" label="Roots" color="blue"></option><option value="d5d5489eea1a491282d6fb893bc84b6d" label="Stores" color="blue"></option></select></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><code>manage</code></td><td><span data-option="ca10bc578ce042d1a07c85ac6ca0125e">Roots, </span><span data-option="d5d5489eea1a491282d6fb893bc84b6d">Stores</span></td><td>Control, modify, and configure an actual structure ('physical' directory or database) itself</td><td><a href="/pages/ri5PBUsJHrSWfQvdWb6n">/pages/ri5PBUsJHrSWfQvdWb6n</a></td></tr><tr><td><code>stats</code></td><td><span data-option="ca10bc578ce042d1a07c85ac6ca0125e">Roots, </span><span data-option="d5d5489eea1a491282d6fb893bc84b6d">Stores</span></td><td>Retrieve statistics about a structure (Root or Store) itself</td><td><a href="/pages/UOIziKDB99IWWpU0pUid">/pages/UOIziKDB99IWWpU0pUid</a></td></tr></tbody></table>

<table data-view="cards"><thead><tr><th></th><th><select multiple><option value="b6c07940b11246b3bd6d778534acea72" label="Roots" color="blue"></option><option value="7649aee1d305403684683ff570d979f1" label="Stores" color="blue"></option><option value="68cf55f60c914a208a5271f65c8478c8" label="Module" color="blue"></option></select></th><th></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><code>recovery</code></td><td><span data-option="b6c07940b11246b3bd6d778534acea72">Roots</span></td><td>Recover failed bulk downloads, and prepare them to restart</td><td><a href="/pages/QjStFkRCKXgLWD1lY19Q">/pages/QjStFkRCKXgLWD1lY19Q</a></td></tr><tr><td><code>migrator</code></td><td><span data-option="b6c07940b11246b3bd6d778534acea72">Roots</span></td><td>Migrate the incompatible file/directory structure of a previous version</td><td><a href="/spaces/etKuLqkz5OWV0AkZjLDp/pages/mWbONoysCtvaNu4GN4vq">/spaces/etKuLqkz5OWV0AkZjLDp/pages/mWbONoysCtvaNu4GN4vq</a></td></tr><tr><td><code>import</code></td><td><span data-option="b6c07940b11246b3bd6d778534acea72">Roots, </span><span data-option="68cf55f60c914a208a5271f65c8478c8">Module</span></td><td>Import and prepare a store from a previously exported archive file</td><td><a href="/pages/dNNHdkCG2AVlQpTdqMT6">/pages/dNNHdkCG2AVlQpTdqMT6</a></td></tr><tr><td><code>download</code></td><td><span data-option="7649aee1d305403684683ff570d979f1">Stores</span></td><td>Prepare, start, and manage a store's bulk downloads</td><td><a href="/pages/NL6IkXezlNFKRvn948DA">/pages/NL6IkXezlNFKRvn948DA</a></td></tr><tr><td><code>metadata</code></td><td><span data-option="7649aee1d305403684683ff570d979f1">Stores</span></td><td>A simple key-value pair store designed for storing simple, store related information</td><td><a href="/pages/X3FJe4OjIZh4wpucEFk1">/pages/X3FJe4OjIZh4wpucEFk1</a></td></tr><tr><td><code>export</code></td><td><span data-option="7649aee1d305403684683ff570d979f1">Stores, </span><span data-option="68cf55f60c914a208a5271f65c8478c8">Module</span></td><td>Export a store to an archive file, for future importing</td><td><a href="/pages/44zofqBvlqr0bRZoNJaJ">/pages/44zofqBvlqr0bRZoNJaJ</a></td></tr></tbody></table>

{% hint style="info" %}
Looking to modify the underlying databases youself?

This is not recommended, as they are carefully crafted to work with FMTC internal state. But, if you've read through the FMTC code and understand it, and you need to, you may import the internal APIs usually intended for modules.

* Import 'fmtc\_module\_api.dart' from the same package
* Install [Isar](https://isar.dev/)
  {% endhint %}


# Management

Applies to Roots & Stores

```dart
FlutterMapTileCaching.instance.rootDirectory.manage; // Roots
FlutterMapTileCaching.instance('storeName').manage; // Stores
```

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootManagement-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreManagement-class.html>" %}


# Statistics

Applies to Roots & Stores

```dart
FlutterMapTileCaching.instance.rootDirectory.stats; // Roots
FlutterMapTileCaching.instance('storeName').stats; // Stores
```

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootStats-class.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreStats-class.html>" %}

## Watching For Changes

```dart
FMTC.instance.rootDirectory.stats.watchChanges(); // Roots
FMTC.instance('storeName').stats.watchChanges(); // Stores
```

It is possible to watch for changes in structures, which can be useful to create dynamic UIs (using `StreamBuilder`s) that consume the other statistics. There are lots of customization options to increase efficiency and reduce junk/useless rebuilds.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootStats/watchChanges.html>" %}

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/StoreStats/watchChanges.html>" %}


# Metadata

Applies only to Stores

```dart
FlutterMapTileCaching.instance('storeName').metadata;
```

This library provides a very simple persistent key-value pair storage system, designed to store any custom information about the store. These are stored alongside tiles in the tile database.

For example, your application may use one store per `urlTemplate`, in which case, the URL can be stored in the metadata.

{% hint style="info" %}
Remember that `metadata` does not have any effect on internal logic: it is simply an auxiliary method of storing any data that might need to be kept alongside a store.
{% endhint %}

{% hint style="success" %}
Both asynchronous and synchronous versions of the below methods are available.
{% endhint %}

## Add

Add a new key-value pair to the store. For example:

```dart
    add(
        key: String,
        value: String,
    );
```

## Read

Read all the key-value pairs from the store, and return them in a `Map<String, String>`. For example:

```dart
    read; // This is a getter
```

## Remove

Remove a key-value pair from the store. For example:

```dart
    remove(key: String);
```

## Reset

Remove all the key-value pairs from the store. For example:

```dart
    reset();
```


# Recovery

Applies only to Roots

```dart
FlutterMapTileCaching.instance.rootDirectory.recovery;
```

The recovery system is designed to rescue failed bulk downloads, in the event of an unexpected error - such as a fatal crash or other external event.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/RootRecovery-class.html>" %}


# flutter\_map Integration

Stores also have the method `getTileProvider()`. This is the point of integration with flutter\_map, providing browse caching through a custom image provider, and can be used as so:

```dart
import 'package:flutter_map/flutter_map.dart';

TileLayer(
    // urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
    // userAgentPackageName: 'com.example.app',
    tileProvider: FMTC.instance('storeName').getTileProvider(),
    // Other parameters as normal
),
```

## Tile Provider Settings

This method (and others) optionally take a `FMTCTileProviderSettings`. These configure the behaviour of the tile provider. Defaults to the settings specified in the [Global Settings](/v8/usage/global-settings), or the package default (see table below) if that is not specified.

`FMTCTileProviderSettings` can take the following arguments:

<table data-card-size="large" data-view="cards"><thead><tr><th>Parameter</th><th>Description</th><th>Default</th></tr></thead><tbody><tr><td><code>behavior</code>: <a href="#cache-behavior"><code>CacheBehavior</code></a></td><td>Determine the logic used during handling storage and retrieval of browse caching</td><td><code>CacheBehavior.cacheFirst</code></td></tr><tr><td><code>cachedValidDuration</code>: <code>Duration</code></td><td>Length of time a tile remains valid, after which it must be fetched again (ignored in <code>onlineFirst</code> mode)</td><td><code>const Duration(days: 16)</code></td></tr><tr><td><code>maxStoreLength</code>: <code>int</code></td><td>Maximum number of tiles allowed in a cache store (deletes oldest tile)</td><td><code>0</code>: disabled</td></tr><tr><td><code>obscuredQueryParams</code>: <code>List&#x3C;String></code></td><td>See <a data-mention href="#obscuring-query-parameters">#obscuring-query-parameters</a></td><td><code>[]</code>: empty</td></tr></tbody></table>

### Cache Behavior

This enumerable contains 3 values, which are used to dictate which logic should be used to store and retrieve tiles from the store.

<table><thead><tr><th width="174">Value</th><th>Explanation</th></tr></thead><tbody><tr><td><code>cacheFirst</code></td><td><p>Get tiles from the local cache if possible.</p><p>Only uses the Internet if it doesn't exist, or to update it if it has expired.</p></td></tr><tr><td><code>onlineFirst</code></td><td><p>Get tiles from the Internet if possible.</p><p>Updates every cached tile every time it is fetched (ignores expiry).</p></td></tr><tr><td><code>cacheOnly</code></td><td><p>Only get tiles from the local cache, and throw an error if not found.</p><p>Recommended for dedicated offline modes.</p></td></tr></tbody></table>

### Obscuring Query Parameters

{% hint style="success" %}
A backport of this functionality to v6 is also available - see [this branch on GitHub](https://github.com/JaffaKetchup/flutter_map_tile_caching/tree/v6-backporting), and install it through GitHub: [/pages/kRBEuMh9Osof6GjNQWDE#from-github.com](https://fmtc.jaffaketchup.dev/v8/usage/pages/kRBEuMh9Osof6GjNQWDE#from-github.com "mention").
{% endhint %}

If you've got a value (such as a token or a key) in the URL's query parameters (the key-value pairs list found after the '?') that you need to keep secret or that changes frequently, make use of `obscuredQueryParams`.

Pass it the list of query keys who's values need to be removed/omitted/obscured in storage. For example, 'api\_key' would remove the 'api\_key', and any other characters until the next key-value pair, or the end of the URL, as seen below:

<pre><code>https://tile.myserver.com/{z}/{x}/{y}?api_key=001239876&#x26;mode=dark
<strong>https://tile.myserver.com/{z}/{x}/{y}?&#x26;mode=dark
</strong></code></pre>

{% hint style="info" %}
Since v3, FMTC relies on URL equality to find tiles within a store during browsing. This method is therefore necessary in cases where a token changes periodically.
{% endhint %}

## Check If A Tile Is Cached

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/FMTCTileProvider/checkTileCached.html>" %}


# Global Settings

```dart
FlutterMapTileCaching.settings;
```

Settings change the functionality of FMTC throughout a project, and can be useful to setup customizations that are needed across multiple calls.

For example, a project with multiple [`getTileProvider()`](/v8/usage/integration) calls can configure the [flutter\_map Integration](/v8/usage/integration#tile-provider-settings) here, to reduce duplication and improve maintainability.&#x20;

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/FMTCSettings-class.html>" %}


# Introduction

This package provides the ability to download areas of maps, known as 'regions' throughout this documentation. There are built in region shapes that even large apps, such as Google Maps, don't have!

Downloading is extremely efficient and fast, and uses multiple threads and isolates to achieve write speeds of hundreds of tiles per second (if the network/server speed allows).

After downloading, tiles are stored in the same place as when Browse Caching, meaning that no extra setup is needed to use them in a map (other than the usual [flutter\_map Integration](/v8/usage/integration)).

{% hint style="warning" %}
Before using FMTC, ensure you comply with the appropriate rules and ToS set by your tile server. Failure to do so may lead to a permenant ban, or any other punishment.

This library and/or the creator(s) are not responsible for any violations you make using this package.

OpenStreetMap's can be [found here](https://operations.osmfoundation.org/policies/tiles): specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
{% endhint %}

## 4 Steps To Downloading

1. [Create a region based on the user's input](/v8/bulk-downloading/regions)
2. [Convert that region into a downloadable region](/v8/bulk-downloading/prepare)
3. Start downloading that region, either in the [foreground](/v8/bulk-downloading/foreground) or [background](/v8/bulk-downloading/background)
4. [Listen for progress events to update your user](/v8/bulk-downloading/foreground/progress)

## Available APIs

All APIs listed in this section are children of the `download` getter.

<table><thead><tr><th width="238">API Member</th><th>Explanation</th></tr></thead><tbody><tr><td><a href="/pages/tkW40lqI0f14YpAXkgO0"><code>startForeground()</code></a></td><td>Start a download in the foreground</td></tr><tr><td><a href="/pages/qK0PQ7EoofYzAS1seUzz"><code>startBackground()</code></a></td><td>Start a download in the background</td></tr><tr><td><a href="/pages/z6CsHSz41ooUzs1UzOxf#checking-number-of-tiles"><code>check()</code></a></td><td>Check the number of tiles in a certain region</td></tr><tr><td><a href="/pages/nbxvKQQvLenaVe8V3Y8W"><code>cancel()</code></a></td><td>Cancel any ongoing downloads</td></tr></tbody></table>

## Recovery

The recovery system is designed to support bulk downloading, and provide some form of recovery if the download fails unexpectedly - this might happen if the app crashes, for example.

Read more about the recovery system here:

{% content-ref url="/pages/QjStFkRCKXgLWD1lY19Q" %}
[Recovery](/v8/usage/roots-and-stores/recovery)
{% endcontent-ref %}


# Create A Region

Creating regions is designed to be easy for the user and you (the developer).

The [Example Application](/v8/get-started/example-application) contains a great way you might want to allow your users to choose a region to download, and it shows how to use Provider to share a created region and the number of approximate tiles it has to a download screen.

## Types Of Region

All regions (before conversion to `DownloadableRegion`) implement `BaseRegion`.

{% tabs %}
{% tab title="Rectangle" %}
The most basic type of region, defined by two North West and South East coordinates that create a `LatLngBounds`.

```dart
final region = RectangleRegion(
    LatLngBounds(
        LatLng(), // North West
        LatLng(), // South East
    ),
);
```

{% hint style="info" %}
Skewed parallelograms (rectangles with a 3rd control point) or rotated rectangles are not currently supported
{% endhint %}
{% endtab %}

{% tab title="Circle" %}
A more advanced type of region, defined by a center coordinate and radius (in kilometers).

```dart
final region = CircleRegion(
    LatLng(), // Center
    0, // KM Radius
);
```

If you have two coordinates, one center, and one on the edge of the circle you want, you can use ['latlong2's `Distance.distance()`](https://pub.dev/documentation/latlong2/latest/latlong2/Distance/distance.html) method, as below:

```dart
final region = CircleRegion(
    LatLng(), // Center
    const Distance(roundResult: false).distance(
        LatLng(), // Center
        LatLng(), // Edge Coord
    ) / 1000; // Convert to KM
);
```

{% endtab %}

{% tab title="Line" %}
The most advanced type of region, defined by a list of coordinates and radius (in meters).

```dart
final region = LineRegion(
    [LatLng(), LatLng(), ...], // Series of coordinates
    0, // M Radius
);
```

{% endtab %}
{% endtabs %}

After you've created your region, you can convert it to a drawable polygon (below), or [convert it to a `DownloadableRegion`](/v8/bulk-downloading/prepare) ready for downloading.

## Converting To Drawable Polygons

All `BaseRegions` can be drawn on a map with minimal effort from you or the user, using `toDrawable()`.

Internally, this uses the `toOutline(s)` method to generate the points forming the `Polygon`, then it places this/these polygons into a `PolygonLayer`.


# Prepare For Downloading

{% hint style="warning" %}
Before using FMTC, ensure you comply with the appropriate rules and ToS set by your tile server. Failure to do so may lead to a permenant ban, or any other punishment.

This library and/or the creator(s) are not responsible for any violations you make using this package.

OpenStreetMap's can be [found here](https://operations.osmfoundation.org/policies/tiles): specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
{% endhint %}

[`BaseRegions`](/v8/bulk-downloading/regions) must be converted to `DownloadableRegions` before they can be used to download tiles.

These contain the original `BaseRegion`, but also some other information necessary for downloading, such as zoom levels and URL templates.

See this basic example:

```dart
final downloadable = region.toDownloadable(
    1, // Minimum Zoom
    18, // Maximum Zoom
    TileLayer(
        // Use the same `TileLayer` as in the displaying map, but omit the `tileProvider`
        urlTemplate: 'https://api.mapbox.com/styles/v1/jaffaketchup/cle0ehaiz00j101qqr14f8mm3/tiles/256/{z}/{x}/{y}@2x',
        userAgentPackageName: 'com.example.app',
    ),
    // Additional parameters if necessary
),
```

## Additional Parameters

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/BaseRegion/toDownloadable.html>" %}

### Sea Tile Removal

By not storing pure tiles of sea, we can save a bunch of space on the user's device with every download. But how to do this?

Well, this package does it by analysing the bytes of the tile and checking if it's identical to a sample taken at lat/lng 0, 0 (Null Island) with zoom level 17. This tile should always be sea, and therefore any matching tile must also be sea.

In this way, we can delete tiles after we've checked them, if they are indeed sea. This method also means that tiles with ferry paths and other markings remain safe.

{% hint style="info" %}
Sea Tile Removal does not reduce time or data consumption. Every tile must still be downloaded to check it.
{% endhint %}

## Checking Number Of Tiles

Before downloading the region, you can count the number of tiles it will attempt to download. This is done by the `check()` method.

The method takes the `DownloadableRegion` generated above, and will return an `int` number of tiles.


# Start In Foreground

{% hint style="warning" %}
Before using FMTC, ensure you comply with the appropriate rules and ToS set by your tile server. Failure to do so may lead to a permenant ban, or any other punishment.

This library and/or the creator(s) are not responsible for any violations you make using this package.

OpenStreetMap's can be [found here](https://operations.osmfoundation.org/policies/tiles): specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
{% endhint %}

```dart
FMTC.instance('storeName').download.startForeground();
```

To start downloading tiles, you must [Listen For Progress](/v8/bulk-downloading/foreground/progress), even if you do not plan to use the [Listen For Progress](/v8/bulk-downloading/foreground/progress#available-statistics).

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/DownloadManagement/startForeground.html>" %}


# Buffering

Available since v7

{% hint style="info" %}
Buffering reduces the total download time, at the expense of increased memory usage.
{% endhint %}

## Without Buffering

Without buffering, every tile is written directly to the database before continuing to the next one (within each simultaneous thread). This requires a write transaction for every tile, which are relatively slow. Without buffering, the database write speed is *usually* the limiting factor in the download speed.

## With Buffering

To avoid this problem, buffering can be used. Tiles are written to an intermediate buffer before being written to the database, in bulk. This means a transaction is only needed for every bulk write operation, which can lead to huge speed improvements (>2x is possible). However, there are two major cons that you should consider before implementing this in your application:

* **Memory usage increases significantly**\
  When in use, the Memory usage graph within DevTools will likely look like a sawtooth wave. The peak memory usage may be too high for some devices, so you should consider your audience.
* **An app crash can lead to data loss**\
  Tiles in the buffer will be lost in the event of an app crash, meaning their download will have been wasted. Tiles that have been written previously will not be lost.

It may be appropriate to leave the decision up to each user individually. In this case, ensure you thoroughly explain these cons to the user, to allow them to make an informed decision. Alternatively, you might make a decision based on the platform. Desktop platforms are likely to have enough RAM capable of holding the buffer, whereas some older mobile devices may struggle.

## Using Buffering

Buffering is disabled by default, and can be enabled in the `startForeground` method call (the property is not part of the `DownloadableRegion`).

{% hint style="info" %}
Buffering is not supported by background downloading, as maximizing speed isn't usually a priority for background downloads.
{% endhint %}

Buffering can be defined by one of two types of limit, shown below. Neither has a disadvantage in terms of performance, but memory allows finer grain control, at the expense of being less obvious to the user.

* Memory (buffer bytes size)
* Tiles (buffer length)


# Listen For Progress

To listen for progress events, you can listen to the `Stream` of `DownloadProgress` events returned by the `startForeground()` method, which contains useful statistics about the download.

Listening can be done through any method, such as `listen()` or the `await for` loop.

{% embed url="<https://pub.dev/documentation/flutter_map_tile_caching/latest/flutter_map_tile_caching/DownloadProgress-class.html>" %}


# Start In Background

fmtc\_plus\_background\_downloading Module

{% hint style="success" %}
The [`fmtc_plus_background_downloading`](https://github.com/JaffaKetchup/fmtc_plus_background_downloading) module is required to use the background bulk downloading functionality.

Note this functionality is only available on Android.

See the [Additional Setup](/v8/get-started/additional-setup#fmtc_plus_background_downloading-installation-and-setup) instructions to add this module.
{% endhint %}

{% hint style="info" %}
You should read about the [limitations and tradeoffs of background downloading](/v8/bulk-downloading/background/limitations) before you start using it.
{% endhint %}

{% hint style="warning" %}
Before using FMTC, ensure you comply with the appropriate rules and ToS set by your tile server. Failure to do so may lead to a permenant ban, or any other punishment.

This library and/or the creator(s) are not responsible for any violations you make using this package.

OpenStreetMap's can be [found here](https://operations.osmfoundation.org/policies/tiles): specifically bulk downloading is discouraged, and forbidden after zoom level 13. Other servers may have different terms.
{% endhint %}

```dart
FMTC.instance('storeName').download.startBackground();
```

## Available Parameters

{% embed url="<https://pub.dev/documentation/fmtc_plus_background_downloading/latest/fmtc_plus_background_downloading/FMTCBackgroundDownloadingModule/startBackground.html>" %}

## Additional Preparation

You should also wrap your application's root widget (such as a `Scaffold`) with the [`FMTCBackgroundDownload`](https://pub.dev/documentation/flutter_map_tile_caching/5.0.0-dev.6/fmtc_advanced/FMTCBackgroundDownload-class.html) widget.

This is designed to stop the app from terminating when it is taken off the widget tree, such as when the user closes the application. It is safe to leave there even when not downloading: it is intelligent enough to only keep the application alive if there is an ongoing background download.


# Limitations

fmtc\_plus\_background\_downloading Module

## Android Only

Unfortunately, background downloading is available on Android only, due to the strict limitations imposed by iOS. This is unlikely to change in the future, especially as I am currently unable to develop for iOS.

In addition, there is no planned support for other platforms.

## Background Processes

There is some confusion about the way background process handling works on Android, so let me clear it up for you: it is confusing.

Each vendor (eg. Samsung, Huawei, Motorola) has their own methods of handling background processes.

Some manage it by providing the bare minimum user-end management, resulting in process that drain battery because they can't be stopped easily; others manage it by altogether banning/strictly limiting background processes, resulting in weird problems and buggy apps; many manage it by some layer of control on top of Android's original controls, making things more confusing for everyone.&#x20;

Therefore there is no guaranteed behaviour when using this functionality. You can see how many vendors will treat background processes here: [dontkillmyapp.com](https://dontkillmyapp.com/); you may wish to link your users to this site so they can properly configure your app to run in the background.

To try and help your users get to the right settings quicker, use the `requestIgnoreBatteryOptimizations()` method before starting a background download. This will interrupt the app with either a dialog or a settings page where they can opt-in to reduced throttling. There is no guarantee that this will work, but it should help: this is not required and the background download will still *try* to run even if the user denies the permissions.

Internally, a foreground service is actually used. This allows the service to run as long as the app hasn't been force stopped.

## Recovery Effectiveness

The effectiveness of the [Recovery](/v8/usage/roots-and-stores/recovery) system is reduced by background downloading.

If the user leaves the application, then the recovery system may report the ongoing background download as failed, as it has no way of knowing about it. If the user tries to retry the download, both downloads may then fail, and the recovery system may fail also.

There is no way of resolving this situation. You may prefer to disable recovery on background downloads.

## Progress Events

Unlike foreground downloading, where you can [Listen For Progress](/v8/bulk-downloading/foreground/progress), background downloading does not provide any way to do this, so it is much less customisable.

The download progress notification only displays the percentage progress and number of tiles attempted/max number of tiles (see [Listen For Progress](/v8/bulk-downloading/foreground/progress#available-statistics)).

## No Buffering Support

[Buffering](/v8/bulk-downloading/foreground/buffering) is not supported by background downloads.


# Cancel Download

If you need to stop a bulk download early, you can use the `cancel()` method to safely exit. No more tiles will be downloaded, and any tiles still within the buffer (see [Buffering](/v8/bulk-downloading/foreground/buffering)) will be written.

{% hint style="info" %}
There is no support to pause downloads, nor is there planned support.
{% endhint %}


# Introduction

fmtc\_plus\_sharing Module

{% hint style="success" %}
The [`fmtc_plus_sharing`](https://github.com/JaffaKetchup/fmtc_plus_sharing) module is required to use the import/export functionality.

See the [Additional Setup](/v8/get-started/additional-setup#fmtc_plus_sharing-installation-and-setup) instructions to add this module.
{% endhint %}

{% hint style="warning" %}
Note that some tile servers, such as Mapbox, forbid the sharing of their cached tiles, but this should still be acceptable as long as a user only imports their own exports (for example, backup purposes).
{% endhint %}

It is possible to [export](/v8/import-and-export/exporting) an entire store (including tiles and metadata) to a standalone file that can be easily shared and distributed between devices, then [imported](/v8/import-and-export/importing) on any device.

For example, they can be used to create backup systems, sharing systems, or to distribute a preset package of tiles to all users without worrying about managing IO or managing assets!


# Importing

fmtc\_plus\_sharing Module

It is possible to re-import a store generated by an [Exporting](/v8/import-and-export/exporting).

{% hint style="warning" %}
Attempting to import a damaged/corrupted store may result in a fatal app crash, or major errors at least.

See [Initialisation](/v8/usage/initialisation#initialisation-safety) for more information about how this may be handled in future.
{% endhint %}

## With Platform GUI (`withGUI`)

{% embed url="<https://pub.dev/documentation/fmtc_plus_sharing/latest/fmtc_plus_sharing/FMTCImportSharingModule/withGUI.html>" %}

## With A Known `File` (`manual`)

{% embed url="<https://pub.dev/documentation/fmtc_plus_sharing/latest/fmtc_plus_sharing/FMTCImportSharingModule/manual.html>" %}

## Collision/Conflict Resolution

In the event that a store with the same name already exists as the store that is trying to be imported, FMTC has only simple conflict resolution behaviour.

When a collision is detected, the defined callback `collisionHandler` is called asynchronously, with the filename of the import file in addition to the real name of the contained store. It can return either:

* `true`: Override the *entire* existing store and its contents
* `false`: Skip/cancel the import


# Exporting

fmtc\_plus\_sharing Module

Exporting a store copies the internal store database, 'compresses' it slightly, then renames it appropriately. The files are in the binary format that [Isar](https://isar.dev/) uses, so they cannot easily be read or modified.

They can have any file extension applied to them - '.fmtc' is used in the example application. The name of the file dictates the name of the store that will be used when importing (without the 'export\_' prefix, if still present).

## With Platform GUI (`withGUI`)

{% embed url="<https://pub.dev/documentation/fmtc_plus_sharing/latest/fmtc_plus_sharing/FMTCExportSharingModule/withGUI.html>" %}

## With A Known `File` (`manual`)

{% embed url="<https://pub.dev/documentation/fmtc_plus_sharing/latest/fmtc_plus_sharing/FMTCExportSharingModule/manual.html>" %}


# v7 -> v8 Migration

{% hint style="success" %}
v8 brings major performance & stability improvements, along with support for Isar v3.1 and 'flutter\_map' v4.
{% endhint %}

Some migrations are necessary for a **small number of users**. These migrations are listed below, theoretically in decreasing number of affected users.

The underlying storage structure is directly compatible with v7, and so migration for that is not required.

## Importing

{% hint style="info" %}
The [`fmtc_plus_sharing`](https://github.com/JaffaKetchup/fmtc_plus_sharing) module is required to use the import/export functionality.

See the [Additional Setup](/v8/get-started/additional-setup#fmtc_plus_sharing-installation-and-setup) instructions to add this module.
{% endhint %}

* **Return type is now `ImportResult`**\
  This contains both the real store name (may be different to the filename) and whether the import was successful.
* **Collision handlers are now called with an additional argument**\
  The filename and real store name are now both passed to the collision handler. See [Importing](/v8/import-and-export/importing#collision-conflict-resolution).

## Initialisation

`FMTCInitialisationException`'s fields have changed to be more useful in debugging initialisation issues, both internally and externally. If *processing* these issues manually, you'll need to migrate. See the in-code documentation for more information.

## Custom `HttpClient` Usage

FMTC now supports HTTP/2, through ['http\_plus'](https://pub.dev/packages/http_plus)! HTTP/2 support is enabled by default, with a fallback to HTTP/1.1 (both with a timeout of 5 seconds).&#x20;

In many of the places where `HttpClient`s where previously accepted as arguments, `BaseClient` subtypes are now required. To continue using a custom `HttpClient`, wrap it with an `IOClient`.


# v6 -> v7 Migration

{% hint style="warning" %}
v7 was [left in an broken state](https://web.archive.org/web/20230418144459/https://fmtc.jaffaketchup.dev/) due to a package upgrading its version without following semantic versioning, meaning that the pub package resolver could never successfully resolve a working v7 package.

v8 contains all functionality from v7. Therefore, follow these migrations, then the [v7 -> v8 Migration](/v8/migration/v7-to-v8-migration) instructions to upgrade to v8 (retaining the `StoreManagement.migrator`) method.

v7 documentation is no longer available.
{% endhint %}

v6 and v7 have significantly different underlying storage systems, and therefore different APIs. Pre-v6 uses a multi-directory filesystem-based structure, whereas v7 uses a multi-database structure based on [Isar](https://isar.dev/).

This page highlights the *biggest changes* made that will affect the most users - feature additions are not included. Smaller changes are described by in-code documentation or should be self explanatory.

{% hint style="info" %}
Note that the code samples representing the new API do not always include all (new) properties, only those that have changed.
{% endhint %}

{% hint style="success" %}
Don't forget to add and configure the migrator method (`StoreManagement.migrator`) before publishing your app.
{% endhint %}

## Changes

### Initialisation & Root Directory System

Due to the change in the underlying storage system, initialisation has changed to be asynchronous itself, meaning that the previous method of defining the `rootDirectory` was now overly complicated. So the `RootDirectory` system has also been simplified.

{% tabs %}
{% tab title="v6" %}

```dart
FlutterMapTileCaching.initialise(
    await RootDirectory.normalCache,
    // ...
);
final bool isReady = await FMTC.instance.rootDirectory.manage.ready;
```

{% endtab %}

{% tab title="v7" %}
{% code overflow="wrap" %}

```dart
await FlutterMapTileCaching.initialise(
    rootDirectory: null, // Optional for most applications
    // ...
);
// Safely assume that the root directory is ready, until `.manage.delete()` has been called
```

{% endcode %}
{% endtab %}
{% endtabs %}

### Store Management

Some methods have been removed from store management due to limitations in Isar. The deprecation documentation in-code suggests replacements.

### Statistic Watching

Changes have been made, which has improved cross-platform stability and performance. Some properties have changed: consult the documentation for more information. Migration should be self explanatory.

### Global Settings

Validation options have been removed, as any store name (within UTF8) is now acceptable, because the limitations of the filesystem have been removed.

Some Isar database options have been added to manage the database files themselves. These have sensible defaults, although you should check them to make sure they fit your use-case.

### Download Progress

With the introduction of [Buffering](/v8/bulk-downloading/foreground/buffering) for bulk downloading, there are now two additional statistics. The existing statistics `successfulTiles` and `successfulSize` will remain work, but with altered functionality: they now report the number of downloaded, not-necessarily persisted (still in buffer), tiles and size respectivley.

`persistedTiles` and `persistedSize` now report the number of tiles and size that has actually been written to the database.

### Import Collision Handling

Handling collisions with existing stores during imports has gotten easier, with a built-in callback parameter.

## Removals

### Import/Export & Background Downloading Functionality From Base

These functionalities have been separated into their own modules, in order to simplify the installation of this package.

To add this functionality again, see [Installation](/v8/get-started/installation) and [Additional Setup](/v8/get-started/additional-setup).

If you don't use this functionality in your app, you can undo the [Additional Setup](/v8/get-started/additional-setup) instructions related to these modules.

### Statistic Caching

Methods and fields related to statistic caching have been removed, along with the underlying statistic cache system. This is because the new Isar databases are fast enough to calculate statistics, to the point where caching would likely result in reduced performance.


# Known Issues

{% embed url="<https://github.com/JaffaKetchup/flutter_map_tile_caching/issues/>" %}

{% hint style="success" %}
Where issues originate in FMTC, I try my hardest to fix them quickly where possible, or provide workarounds if necessary.

Some problems will take longer than others to fix, so please be patient. If you think you can fix the issue, please get in touch and/or create a PR - contributions are always welcome!

Where they don't originate directly in FMTC, I consider donating or creating a bounty to get the bug resolved, if I can't help fix it myself. This money can only come from [donations](/v8#supporting-me) and [alternative license](/v8#proprietary-licensing) payments to FMTC.
{% endhint %}

## Isar Stability Issues

{% hint style="warning" %}
**FMTC is currently somewhat unstable for applications with a wide public reach, due to some issues with the Isar dependency.**

These issues are being worked on behind the scenes, but unfortunately, there is no planned release date for the v4, which is planned to include these fixes.

FMTC should behave correctly on the majority of devices, but it can cause fatal app crashes on some devices. v8 is much more stable than v7, as it depends on Isar 3.1.0.

If you're significantly concerned about stability, consider using the '[v6-backporting](https://github.com/JaffaKetchup/flutter_map_tile_caching/tree/v6-backporting)' branch instead of v8. v6 has significantly worse performance, and has some other major downsides, but is likely to be more stable across more platforms. The v6 [documentation](broken://spaces/YFI6k92MXbd87FM5cPCk) is still available.
{% endhint %}


# Credits

This project is currently maintained by JaffaKetchup (Luka S). I am currently a maintainer of flutter\_map, but this project has no other internal links.

Thanks to all contributors, and 'bugDim88' who originally came up with the idea and created a PR for flutter\_map. When that PR was closed (it was decided a plugin would be more suitable), I took over the project. Also thanks to all of the 3rd party dependencies and their maintainers.

## Sponsors

{% hint style="info" %}
Please see [flutter\_map\_tile\_caching](/v8#supporting-me) for more information about why and how to sponsor me, for any amount you think is suitable
{% endhint %}

Many thanks to all my sponsors, not matter how much or how little they donated (in no particular order):

* [@tonyshkurenko](https://github.com/tonyshkurenko)
* [@Mmisiek](https://github.com/Mmisiek)
* [@huulbaek](https://github.com/huulbaek)
* [@andrewames](https://github.com/andrewames)
* [@ozzy1873](https://github.com/ozzy1873)
* [@eidolonFIRE](https://github.com/eidolonFIRE)
* [@weishuhn](https://github.com/weishuhn)
* [@mohammedX6](https://github.com/mohammedX6)
* *+ 3 anonymous or private donors*

## Privacy & Cookie Policy

3rd party cookies are in use by default, for the purpose of internally tracking visits to this site. We use Google Analytics and GitBook's own built-in analytics to perform this. Data collected is kept confidential to the author of this site, and is only used for improvement/insight purposes.

The Google Analytics property in use is not connected to any Ad tracking/provider any more than by default. No ads are shown on this site.

Please get in touch if you have more questions!


