Skip to main content

Overview

Storage tracks presentation events across sessions. This guide shows you how to implement the PresentumStorage interface.

Implementation

Use this generic go-to SharedPreferences storage implementation for any presentum:
import 'dart:async';

import 'package:presentum/presentum.dart';
import 'package:shared_preferences/shared_preferences.dart';

typedef PersistentPresentumStorageKey<
  S extends PresentumSurface,
  V extends PresentumVisualVariant
> = (String itemId, S surface, V variant);

extension type PersistentPresentumStorageKeys<
  S extends PresentumSurface,
  V extends PresentumVisualVariant
>(PersistentPresentumStorageKey<S, V> key) {
  String get shownCount =>
      '__shown_${key.$1}_${key.$2.name}_${key.$3.name}_count_key__';
  String get lastShown =>
      '__shown_${key.$1}_${key.$2.name}_${key.$3.name}_last_shown_key__';
  String get timestamps =>
      '__shown_${key.$1}_${key.$2.name}_${key.$3.name}_timestamps_key__';
  String get dismissedAt =>
      '__dismissed_${key.$1}_${key.$2.name}_${key.$3.name}_at_key__';
  String get convertedAt =>
      '__converted_${key.$1}_${key.$2.name}_${key.$3.name}_at_key__';

  List<String> get allKeys => [
    shownCount,
    lastShown,
    timestamps,
    dismissedAt,
    convertedAt,
  ];
}

class PersistentPresentumStorage<
  S extends PresentumSurface,
  V extends PresentumVisualVariant
>
    implements PresentumStorage<S, V> {
  PersistentPresentumStorage({required SharedPreferencesWithCache prefs})
    : _prefs = prefs;

  final SharedPreferencesWithCache _prefs;

  @override
  Future<void> clearItem(
    String itemId, {
    required S surface,
    required V variant,
  }) => Future.wait(
    PersistentPresentumStorageKeys((
      itemId,
      surface,
      variant,
    )).allKeys.map(_prefs.remove),
  );

  @override
  FutureOr<DateTime?> getLastShown(
    String itemId, {
    required S surface,
    required V variant,
  }) async {
    final key = PersistentPresentumStorageKeys((
      itemId,
      surface,
      variant,
    )).lastShown;
    final timestampStr = _prefs.getString(key);
    return timestampStr != null ? DateTime.parse(timestampStr) : null;
  }

  @override
  FutureOr<void> recordShown(
    String itemId, {
    required S surface,
    required V variant,
    required DateTime at,
  }) async {
    final keys = PersistentPresentumStorageKeys((itemId, surface, variant));
    final countKey = keys.shownCount;
    final lastShownKey = keys.lastShown;
    final timestampsKey = keys.timestamps;

    final currentCount = _prefs.getInt(countKey) ?? 0;
    final currentTimestamps = _prefs.getStringList(timestampsKey) ?? [];

    await _prefs.setInt(countKey, currentCount + 1);
    await _prefs.setString(lastShownKey, at.toIso8601String());
    await _prefs.setStringList(timestampsKey, [
      ...currentTimestamps,
      at.toIso8601String(),
    ]);
  }

  @override
  FutureOr<int> getShownCount(
    String itemId, {
    required Duration period,
    required S surface,
    required V variant,
  }) async {
    final keys = PersistentPresentumStorageKeys((itemId, surface, variant));
    final timestampsKey = keys.timestamps;
    final timestampStrings = _prefs.getStringList(timestampsKey) ?? [];
    final timestamps = timestampStrings.map(DateTime.parse).toList();
    final cutoff = DateTime.now().subtract(period);
    final count = timestamps.where((t) => t.isAfter(cutoff)).length;

    return count;
  }

  @override
  FutureOr<DateTime?> getDismissedAt(
    String itemId, {
    required S surface,
    required V variant,
  }) async {
    final keys = PersistentPresentumStorageKeys((itemId, surface, variant));
    final timestampStr = _prefs.getString(keys.dismissedAt);
    return timestampStr != null ? DateTime.parse(timestampStr) : null;
  }

  @override
  FutureOr<void> recordDismissed(
    String itemId, {
    required S surface,
    required V variant,
    required DateTime at,
  }) async {
    final keys = PersistentPresentumStorageKeys((itemId, surface, variant));
    await _prefs.setString(keys.dismissedAt, at.toIso8601String());
  }

  @override
  FutureOr<void> recordConverted(
    String itemId, {
    required S surface,
    required V variant,
    required DateTime at,
  }) async {
    final keys = PersistentPresentumStorageKeys((itemId, surface, variant));
    await _prefs.setString(keys.convertedAt, at.toIso8601String());
  }
}

See generic production storage

Complete SharedPreferences implementation