Only this pageAll pages
Powered by GitBook
1 of 35

v9

Loading...

Loading...

Loading...

Loading...

Get Started

Loading...

Loading...

Loading...

Loading...

General

Loading...

Loading...

Loading...

Loading...

Stores & Roots

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Bulk Downloading

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

External

Loading...

Loading...

Loading...

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.

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.

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.

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 '', or the other browse-caching-only flutter_map plugin '' if you need to save even more time at the expense of more external dependencies.

If you're still not sure, please get in touch: ! I'm always happy to offer honest guidance :)

cached_network_image
flutter_map_cache

(Proprietary) Licensing

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.

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.

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!

Quickstart

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.

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.

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

Console/Terminal
flutter pub add flutter_map_tile_caching
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

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

main.dart
import 'package:flutter/widgets.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

Future<void> main() async {
    WidgetsFlutterBinding.ensureInitialized();
    
    await FMTCObjectBoxBackend().initialise(...);
    
    // ...
    
    runApp(MyApp());
}

Create a container that is capable of storing tiles, and can be used to and bulk download.

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

main.dart
    // ...
    
    await FMTCStore('mapStore').manage.create();
    
    // ...

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

Double check that the name of the store specified here is the same as the store created above!

map_view.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',
    tileProvider: FMTCStore('mapStore').getTileProvider(),
    // Other parameters as normal
),

5. Wow! Look at that caching...

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.

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 testing purposes, check out the testing tile server included in the FMTC project: Testing Tile Server.

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.

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.

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.

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.

This project is released under GPL v3. For detailed information about this license, see . summarises the license with the following paragraph:

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 . 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!

Want to see who else uses FMTC? Check out !

1.

2.

Here, we'll use the built-in, default 'backend' storage, which uses . 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.

3.

4.

For example, OpenStreetMap's tile server forbids bulk downloading: . And Mapbox has restrictions on importing/exporting from outside of the user's own device.

To start using FMTC in your own app, please check out the guide instead.

The example application pairs perfectly with the testing tile server included in the FMTC project: !

Note that these artifacts are built automatically from the , so may not reflect the the latest release on pub.dev.

https://www.gnu.org/licenses/gpl-3.0.en.html
choosealicense.com
fmtc@jaffaketchup.dev
Install
Initialise
ObjectBox
Connect to 'flutter_map'
https://operations.osmfoundation.org/policies/tiles
Quickstart
Testing Tile Server
'master' branch
LogoOffline Mapping | v6 | flutter_map Docs

v8 -> v9 Migration

v9 is a complete rewrite of FMTC internals, written over hundreds of hours. It focuses on:

  • improved future maintainability by modularity

  • improved stability & performance across the board

  • support of 'tiles across stores': reduced duplication

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

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.

Removed the FlutterMapTileCaching/FMTC object, in favour of direct usage of FMTCStore and FMTCRoot (which replace StoreDirectory & RootDirectory)

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:

Previous
// 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;
Migrated
// Get a store
final FMTCStore newStore = FMTCStore('storeName');

// Access the root statistics
final RootStats newRootStats = FMTCRoot.stats;
// `FMTCRoot` must now be used immediately, because it is not an object instance

See below for information about migrating initialisation.

Changed the method of initialisation & error handling

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 StackTraces include more useful information.

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

Previous
await FlutterMapTileCaching.initialise(
    errorHandler: (FMTCInitialisationException e) {},
);
Migrated
try {
    await FMTCObjectBoxBackend().initialise();
} catch (error, stackTrace) {
    // Improved error handling
}

For more information, see Initialisation & Error Handling.

Removed support for synchronous operations (and renamed asynchronous operations to reflect this)

These were incompatible with the new Isolated FMTCObjectBoxBackend, they've been removed, in favour of backends implementing their own Isolateion 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 FutureBuilders. However, the members have all been renamed in the same form: *Async is now just *.

Bulk Downloading

Refactored DownloadableRegion & removed RegionType

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.

Plugins

Background downloading plugin deprecated without replacement

'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.

Sharing plugin deprecated, external replacement functionality introduced into core

'package:fmtc_plus_sharing' has been deprecated, and the importing/exporting functionality introduced into the core, as External.

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

Statistics

RootStats & StoreStats members are no longer prefixed with 'root' & 'store'/'cache'

These terms were redundant, and have been removed.

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

Replaced RootStats.watchChanges with watchStores & watchRecovery

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).

Miscellaneous

Removed FMTCSettings

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.

Check out the CHANGELOG: ! This page only covers breaking changes, not feature additions and fixes.

Please consider donating: ! Any amount is hugely appriciated!

https://pub.dev/packages/flutter_map_tile_caching/changelog

Initialisation

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.

If initialising in the main method before runApp is called, ensure you also call WidgetsFlutterBinding.ensureInitialised() prior to the backend initialisation.

main.dart
import 'package:flutter/widgets.dart';
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';

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

Do not call any other FMTC methods before initialisation. Doing so will cause a RootUnavailable error to be thrown.

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.

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.

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.

Installation

FMTC is licensed under GPL-v3.

Depend On

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:

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

Commits available from Git (GitHub) may not be stable. Only use this method if you have no other choice.

Import

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

Also ensure you've followed flutter_map's installation instructions!

https://nightly.link/JaffaKetchup/flutter_map_tile_caching/workflows/main/mainnightly.link
Latest Build Artifacts (thanks )

FMTC relies on a self-contained 'environment', called a , 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() singleton, so initialisation is not required again.

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 .

Looking to start using FMTC in your project? Check out the guide!

From

From

First, add the normal dependency following the instructions. Then, add the following lines to your pubspec.yaml file under the dependencies_override section:

backend
nightly
flutter pub add flutter_map_tile_caching
pubspec.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)
import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart';
// You'll also need to import flutter_map and (likely) latlong2 seperately
(Proprietary) Licensing
Quickstart
From pub.dev
Get Help
Trusted by many
Supporting Me
pub.dev
github.com

flutter_map_tile_caching

A plugin for 'flutter_map' providing advanced offline functionality

Highlights

Trusted by many

How can FMTC elevate my app to the next level?

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

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 ;)

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

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.

I wonder how much it costs the app developers?

FMTC supports bulk downloading from any tile server, 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!


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.

(Proprietary) Licensing

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.

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!

In addition to our generous , FMTC is also trusted and all around the world:

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

All I see are rectangles. I don't want rectangles.

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

For bug reports & feature requests:

For implementation/general support: The #plugin channel on the

For other inquiries and licensing:

◉ 📲

Integrated Caching × Bulk Downloading

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!

  • with

◉ 🏃

Ultra-fast & Performant

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. Existing cached tiles can be displayed on the map almost instantly.

  • Multi-threaded setup to minimize load on main thread, even when browse caching

  • Streamlined internals to reduce memory consumption

  • Successfully downloaded tiles aren't redownloaded when an unexpectedly failed download is recovered

◉ 🧩

Import & Export

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 (& costs) low!

◉ 💖

Quick To Implement & Easy To Experiment

A basic caching implementation can be setup in four quick steps, and shouldn't even take 5 minutes to set-up. Check out our Quickstart instructions.

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: Testing Tile Server!

😄
supporters
💝Supporters
📃(Proprietary) Licensing
GitHub Issues
flutter_map Discord server
fmtc@jaffaketchup.dev

Tips

This page is a work in progress! Check back later for more tips and tricks.


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.

...and many more!

Management

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

final mgmt = FMTCStore('storeName').manage;

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 & 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
Cover
Cover
Cover

LogoStoreManagement class - flutter_map_tile_caching library - Dart API
Pitchero GPS
Lafayette GPS
nventive

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.

final stats = FMTCRoot.stats;

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

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.

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!

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:

  • + more anonymous or private donors

And also a thank you to the businesses listed in Trusted by many, and more, for abiding by the GPL license of this repository.

@tonyshkurenko
@Mmisiek
@huulbaek
@andrewames
@ozzy1873
@eidolonFIRE
@weishuhn
@mohammedX6
@quentinchaignaud
@Mayb3Nots
@T-moz
@micheljung

Introduction

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 testing purposes, check out the testing tile server included in the FMTC project: Testing Tile Server.

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).

It is also simple to understand and implement:

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.

final rec = FMTCRoot.recovery;

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

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 recheck the entire region).

  • or a BaseRegion (with the correct subtype), using toRegion

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.

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

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':

Introduction

FMTC uses a root and stores to structure its data.

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.

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.

Ensure names of stores are consistent across every access. "Typed"/code-generated stores are not provided, to maintain flexibility.

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.

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

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: .

For example, OpenStreetMap's tile server forbids bulk downloading: . And Mapbox has restrictions on importing/exporting from outside of the user's own device.

↳ Optionally, before downloading

↳ Optionally, when testing,

events to update your user

Optionally,

The difference between errors & exceptions extends beyond FMTC, and is a general concept in Dart. See about the difference between Exceptions and Errors.

There is generally a single root (which generally corresponds to a single ), which contains multiple stores. Cached tiles can belong to multiple stores, which keeps duplication minimized.

https://operations.osmfoundation.org/policies/tiles
Create a region based on the user's input
Start downloading that region
try the miniature tile server
control (pause/resume/cancel) the download
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
Create a store
// final store = FMTCStore('storeName');
await FMTCStore('storeName').manage.create(); // Refers to the same store as above

Management: manage

Control the store: create it, delete it, rename it, etc.

Statistics: stats

Retrieve information about the store and its contents

Metadata: metadata

Access simple persistent storage, with no direct influence on FMTC's functioning

Bulk Download: download

Prepare/plan, start, and manage bulk downloads

Integrate With TileLayer

Generate a specialised TileProvider that allows flutter_map to access cached tiles

LogoRootStats class - flutter_map_tile_caching library - Dart API
this post for more information
backend
#types-of-region

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.

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,
                ),
            ],
        );
    }
}

Avoid getting the TileProvider from within the build method, especially if the widget is rebuilt frequently.

It can cause unnecessary errors and worsened performance.

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 , or the package default (see table below) if that is not specified.

FMTCTileProviderSettings can take the following arguments:

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.

Value
Explanation

cacheFirst

Get tiles from the local cache if possible.

Only uses the Internet if it doesn't exist, or to update it if it has expired.

onlineFirst

Get tiles from the Internet if possible.

Updates every cached tile every time it is fetched (ignores expiry).

cacheOnly

Only get tiles from the local cache, and throw an error if not found.

Recommended for dedicated offline modes.

Obscuring Query Parameters

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.

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:

https://tile.myserver.com/{z}/{x}/{y}?api_key=001239876&mode=dark
https://tile.myserver.com/{z}/{x}/{y}?&mode=dark

Do not depend on this method to remove secret information from a URL.

Prefer sending any information (as discussed above) through the HTTP headers. This may improve performance and reliability, and can be considered good practise anyhow.

Check If A Tile Is Cached

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.

final stats = FMTCStore('storeName').stats;

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

Backends

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.


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.

More info coming soon...

Create A Region

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

There are 4 types of BaseRegion:

Prepare For Downloading

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

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.

This figure will not take into account any skipped sea tiles or skipped existing tiles, as those are handled at the time of download.

Convert that region into a downloadable region
Parameter
Description
Default
Parameter
Description
Default
Parameter
Description
Default
Parameter
Description
Default

Only one backend is built-into FMTC: the FMTCObjectBoxBackend. This backend uses the to store data.

RectangleRegions are defined by a LatLngBounds: two opposite LatLngs.

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...

CircleRegions are defined by a center LatLng and radius in kilometers.

If you instead have two coordinates, one in the center, and one on the edge, you can use method, as below:

LineRegions are defined by a list of LatLngs, 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!

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.

This type of region may consume more memory/RAM when generating tiles than other region types.

Polygons should not contain self-intersections. These may produce unexpected results.

Holes are not supported.

RecoverableRegions 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.

must be converted to DownloadableRegions before they can be used to download tiles.

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 .

CustomPolygonRegions are defined by a list of LatLngs defining the outline of a .

For more info, see .

ObjectBox library
final region = CustomPolygonRegion(
    [LatLng(0, 0), LatLng(1, 1), ...], // List of coordinates
);
Line region
Circle region
Custom Polygon region
final downloadableRegion = region.toDownloadable(
    minZoom: 1,
    maxZoom: 18,
    options: TileLayer(
        urlTemplate: '<your tile server>',
        userAgentPackageName: 'com.example.app',
    ),
),
check the number of tiles in the region
Recovery
LogoRootRecovery class - flutter_map_tile_caching library - Dart API
final region = RectangleRegion(
    LatLngBounds(LatLng(0, 0), LatLng(1, 1)),
);
final region = CircleRegion(
    LatLng(0, 0), // Center coordinate
    1, // Radius in kilometers
);
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
);
final region = LineRegion(
    [LatLng(0, 0), LatLng(1, 1), ...], // List of coordinates
    1000, // Radius in meters
);
'latlong2's Distance.distance()
simple polygon
BaseRegions
check the number of tiles in the region
Listen for progress

Determine the logic used during handling storage and retrieval of browse caching

CacheBehavior.cacheFirst

cachedValidDuration: Duration

Length of time a tile remains valid, after which it must be fetched again (ignored in onlineFirst mode)

const Duration(days: 16)

maxStoreLength: int

Maximum number of tiles allowed in a cache store (deletes oldest tile)

0: disabled

obscuredQueryParams: List<String>

See Obscuring Query Parameters

[]: empty

Metadata

Data is interpreted in key-value pair form, where both the key and value are Strings. 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.

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.

final md = FMTCStore('storeName').metadata;

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)

Start Download

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

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 system for safe recovery if the download fails

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.

The 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.

Start Download

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 testing purposes, check out the testing tile server included in the FMTC project: Testing Tile Server.

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

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.

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.

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 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:

Memory Consumption

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.

Data Loss

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.

Data Duplication

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.

Testing Tile Server

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

Avoid making too many costly and slow requests to your chosen tile server during development by using this miniature tile server!

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.

To use this tile server:

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.

  1. Download/compile & start the tile server (no permanent installation required)

  2. Use the following URL to connect to it

    • From the local device: http://localhost:7070/{z}/{x}/{y}.png

  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

behavior:

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 or URL template).

For example, OpenStreetMap's tile server forbids bulk downloading: . And Mapbox has restrictions on importing/exporting from outside of the user's own device.

On Windows or Linux Download a copy of the latest '<platform>-ts' artifact from GitHub Actions, and run the executable inside:

On other platforms Clone the to your device, then run '/tile_server/bin/tile_server.dart' manually

From the same network (on another device): http://<your-local-ip>:7070/{z}/{x}/{y}.png To find your local IP address, follow the

https://operations.osmfoundation.org/policies/tiles
CacheBehavior
CacheBehavior
LogoStoreStats class - flutter_map_tile_caching library - Dart API
LogotoDownloadable method - BaseRegion class - flutter_map_tile_caching library - Dart API
https://nightly.link/JaffaKetchup/flutter_map_tile_caching/workflows/main/main
FMTC GitHub repository
instructions for your OS here

Control Downloads

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

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.

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 resumeing first.

LogoStoreMetadata class - flutter_map_tile_caching library - Dart API
LogostartForeground method - StoreDownload class - flutter_map_tile_caching library - Dart API
the Land Tile 90% chance
the Sea Tile 10% chance

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.

await FMTCRoot.external('~/path/to/file.fmtc').export(['storeName']);
LogoDownloadProgress class - flutter_map_tile_caching library - Dart API

Introduction

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 testing purposes, check out the testing tile server included in the FMTC project: Testing Tile Server.

FMTC does not support exporting tiles to a raw Z/X/Y directory structure that can be read by other programs.

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.

Archives are backend specific. They cannot necessarily be imported by a backend different to the one that exported it.

For example, OpenStreetMap's tile server forbids bulk downloading: . And Mapbox has restrictions on importing/exporting from outside of the user's own device.

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

https://operations.osmfoundation.org/policies/tiles
exported
imported
LogoTileEvent class - flutter_map_tile_caching library - Dart API
LogoStoreDownload class - flutter_map_tile_caching library - Dart API
Logoexport method - RootExternal class - flutter_map_tile_caching library - Dart API

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.

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.

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.

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

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

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).

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.

The same storage pitfalls as import exist. listStores must also duplicate the entire archive.

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.

// final root = FMTCRoot;
final size = await FMTCRoot.stats.realSize;

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

Management of the root is not provided by FMTCRoot. Instead, the backend should implement any necessary methods.

Statistics: stats

Retrieve information about the store and its contents

Recovery: recovery

Recovery from unexpectedly failed bulk downloads

External: external

Export & import store archives

Logoimport method - RootExternal class - flutter_map_tile_caching library - Dart API
LogoImportResult typedef - flutter_map_tile_caching library - Dart API
LogoImportConflictStrategy enum - flutter_map_tile_caching library - Dart API
LogolistStores property - RootExternal class - flutter_map_tile_caching library - Dart API
LogocheckTileCached method - FMTCTileProvider class - flutter_map_tile_caching library - Dart API
LogoSponsor @JaffaKetchup on GitHub SponsorsGitHub
Sponsor Me via GitHub Sponsors