> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appdna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Flutter Remote Config

> Read remote configuration values from the AppDNA Console

<Info>
  **Supported on:** iOS SDK `1.0.61+` · Android SDK `1.0.33+` · Flutter SDK `1.0.3+`
</Info>

The remote config module delivers key-value pairs from the AppDNA Console to your app. Reads are served from an in-memory cache populated at SDK launch, so they return quickly without a network round trip.

## Get a Config Value

```dart theme={null}
final value = await AppDNA.remoteConfig.get("welcome_message");
final welcome = (value as String?) ?? "Hello!";
```

`get` returns `Future<dynamic>` -- the value type matches whatever you configured in the Console (string, number, boolean, or JSON map). Cast to the type you expect and supply a fallback for the `null` case.

### Reading Other Types

```dart theme={null}
final retries = (await AppDNA.remoteConfig.get("max_retries") as int?) ?? 3;
final discount = (await AppDNA.remoteConfig.get("discount_rate") as double?) ?? 0.1;
final promoOn = (await AppDNA.remoteConfig.get("show_promo") as bool?) ?? false;

// JSON values arrive as Map<dynamic, dynamic> from the platform channel.
final raw = await AppDNA.remoteConfig.get("hero_banner");
final banner = raw is Map ? Map<String, dynamic>.from(raw) : null;
```

## Get All Values

```dart theme={null}
final all = await AppDNA.remoteConfig.getAll();
print("Loaded ${all.length} config keys");
```

`getAll` returns a `Map<String, dynamic>` containing every key the SDK currently has cached.

## Force a Refresh

```dart theme={null}
await AppDNA.remoteConfig.refresh();
```

`refresh` pulls the latest config from the server. The SDK already refreshes automatically (see below) -- call this explicitly only when you need fresh values right now (e.g. after a user changes their preferences in another tab).

## Module Access

```dart theme={null}
final remoteConfig = AppDNA.remoteConfig;
```

### Module Methods

| Method      | Signature                               | Description                                            |
| ----------- | --------------------------------------- | ------------------------------------------------------ |
| `get`       | `Future<dynamic> get(String key)`       | Read a single config value (cast to the expected type) |
| `getAll`    | `Future<Map<String, dynamic>> getAll()` | Read every cached key/value pair                       |
| `refresh`   | `Future<void> refresh()`                | Force a refresh from the server                        |
| `onChanged` | `void onChanged(Function callback)`     | Register a callback that fires when values change      |

## Typed Helpers

If you prefer typed accessors at the call site, add a Dart `extension` on the module. This keeps the SDK surface small while giving your codebase a typed feel.

```dart theme={null}
extension TypedRemoteConfig on AppDNARemoteConfigModule {
  Future<String> getString(String key, {String fallback = ''}) async {
    final v = await get(key);
    return (v as String?) ?? fallback;
  }

  Future<int> getInt(String key, {int fallback = 0}) async {
    final v = await get(key);
    if (v is int) return v;
    if (v is num) return v.toInt();
    return fallback;
  }

  Future<double> getDouble(String key, {double fallback = 0.0}) async {
    final v = await get(key);
    if (v is double) return v;
    if (v is num) return v.toDouble();
    return fallback;
  }

  Future<bool> getBool(String key, {bool fallback = false}) async {
    final v = await get(key);
    return (v as bool?) ?? fallback;
  }

  Future<Map<String, dynamic>?> getJson(String key) async {
    final v = await get(key);
    if (v is Map) return Map<String, dynamic>.from(v);
    return null;
  }
}
```

Call them like this:

```dart theme={null}
final welcome =
    await AppDNA.remoteConfig.getString("welcome_message", fallback: "Hello!");
final retries =
    await AppDNA.remoteConfig.getInt("max_retries", fallback: 3);
```

## When Config Refreshes

The SDK refreshes remote config automatically:

* **On app launch** -- if the cached config has expired (default TTL: 1 hour)
* **On return from background** -- same TTL check

If the fetch fails, the SDK continues using cached values. On first launch with no connectivity, bundled config defaults are used.

<Info>
  The config TTL is configurable via `AppDNAOptions(configTTL: 7200)` (in seconds). The default is 3600 seconds (1 hour).
</Info>

## Listen for Config Changes

`onChanged` fires once per refresh whenever the SDK receives updated config from the server. Re-read the keys you care about inside the callback.

```dart theme={null}
AppDNA.remoteConfig.onChanged(() async {
  final show =
      (await AppDNA.remoteConfig.get("show_promo") as bool?) ?? false;
  updatePromoBanner(visible: show);
});
```

## Full Example

```dart theme={null}
import 'package:appdna_sdk/appdna_sdk.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String _title = "Welcome";
  bool _showBanner = false;
  String _bannerText = "";

  @override
  void initState() {
    super.initState();
    AppDNA.remoteConfig.onChanged(_loadConfig);
    _loadConfig();
  }

  Future<void> _loadConfig() async {
    final title =
        (await AppDNA.remoteConfig.get("home_title") as String?) ?? "Welcome";
    final showBanner =
        (await AppDNA.remoteConfig.get("show_promo_banner") as bool?) ?? false;
    final bannerText =
        (await AppDNA.remoteConfig.get("promo_banner_text") as String?) ?? "";

    if (!mounted) return;
    setState(() {
      _title = title;
      _showBanner = showBanner;
      _bannerText = bannerText;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(_title)),
      body: Column(
        children: [
          if (_showBanner)
            MaterialBanner(
              content: Text(_bannerText),
              actions: [
                TextButton(onPressed: () {}, child: const Text("Learn More")),
              ],
            ),
          // Rest of the home screen
        ],
      ),
    );
  }
}
```

<Note>
  Remote config values are managed in the Console under **Settings > Remote Config**. Changes take effect on the next SDK config refresh.
</Note>
