Lobster

Type-safe Firebase-RemoteConfig helper library.

GitHub release Language

Carthage Compatible CocoaPods

Feature

  • Can get a value from RemoteConfig / set a value to RemoteConfig to type-safe.
  • Easy to set default value to RemoteConfig by using key-value subscripting.
  • Custom type available ✨
    • String/Int enum
    • Decodable(read-only) and Codable.
  • Can manage expiration duration of config values.
  • Combine framwork support.

Getting Started

Basic Usage

You can integrate Lobster in a few steps implementation:

1. Define ConfigKey

extension ConfigKeys {
    static let welcomeTitle = ConfigKey<String>("welcome_title")
    static let welcomeTitleColor = ConfigKey<UIColor>("welcome_title_color")
}

2. Register value to Firebase Project

Go to Firebase Project and set values you want to get.

3. Let’s use Lobster

import Lobster

// Set default value
Lobster.shared[default: .welcomeTitle] = "Welcome"
Lobster.shared[default: .welcomeTitleColor] = .black

self.titleLabel.text = Lobster.shared[.welcomeTitle]

// Fetch remote-config
Lobster.shared.fetch { _ in
    dispatchQueue.main.async { [weak self] in
        self?.titleLabel.text = Lobster.shared[.welcomeTitle]
        self?.titleLabel.textColor = Lobster.shared[.welcomeTitleColor]
    }
}

Tips for you

Combine

You can get values from Lobster with Combine’s stream.
Here is a sampl viewmodel class.

import Lobster
import Combine

extension ConfigKeys {
    static let title = ConfigKey<String>("title")
}

final class ViewModel: ObservableObject {
    @Published var title: String
    private var cancellables: Set<AnyCancellable> = []

    init() {
        title = Lobster.shared[.titleText]

        Lobster.shared.combine.fetched(.title)
            .receive(on: RunLoop.main)
            .assign(to: \.title, on: self)
            .store(in: &cancellables)
    }
}

NOTE: You need to install Lobster/Combine before using it.

Get value with subscripting syntax.

Use subscripting syntax.

  • Non-Optional
extension ConfigKeys {
    static let text = ConfigKey<String>("text")
}

// Get value from config.
// If value didn't fetch from remote yet. returns default value (if exists).
let text: String = Lobster.shared[.text]

// Get value from only config.
// it is possible to crash if value didn't fetch from remote yet.
let text: String = Lobster.shared[config: .text]

// Get value from only default.
// It is possible to crash if the default value is not set yet.
let text: String = Lobster.shared[default: .text]

// [safe:], [safeConfig:], [safeDefault:] subscripting syntax.
// It is safe because they return nil if they have no value.(return type is `Optional<T>`.)
let text: String? = Lobster.shared[safe: .text]
let text: String? = Lobster.shared[safeConfig: .text]
let text: String? = Lobster.shared[safeDefault: .text]
  • Optional
extension ConfigKeys {
    static let textOptional = ConfigKey<String?>("text_optional")
}

let text: String? = Lobster.shared[.textOptional]
let text: String? = Lobster.shared[config: .textOptional]
let text: String? = Lobster.shared[default: .textOptional]

Set Default value

You can set default values using subscripting syntax or plist.

// Set default value using `[default:]` syntax.
Lobster.shared[default: .titleText] = "Cart Items"
Lobster.shared[default: .titleColor] = .black

// or load from `defaults.plist`
Lobster.shared.setDefaults(fromPlist: "defaults")

Set debug mode

// Enable debug mode (development only)
Lobster.shared.debugMode = true
Lobster.shared.fetchExpirationDuration = 0.0

isStaled

If you set isStaled to true, Lobster will fetch remote value ignoring fetchExpirationDuration.
That is, You can retrieve config values immediately when you call fetch after You set isStales to true.
And isStaled will be set to false after fetched remote value.

Lobster.shared.fetchExpirationDuration = 60 * 12

Lobster.shared.isStaled = true

// Default expire duration is 12 hours.
// But if `isStaled` set to true,
// Lobster fetch values from remote ignoring expire duration.
Lobster.shared.fetch()

Supported types

Lobster supports more types as default followings:

  • String
  • Int
  • Float
  • Double
  • Bool
  • Data
  • URL
  • UIColor
  • enum(String/Int)
  • Decodable Object
  • Codable Object
  • Collection(Array)
    • String
    • Int
    • Float
    • Double
    • Bool
    • Data
    • URL
    • enum(String/Int)
    • Decodable Object
    • Codable Object

TODO

  • [ ] CGPoint
  • [ ] CGSize
  • [ ] CGRect
  • [ ] Dictionary

URL

Supports text:

UIColor

Supports only HEX string like "#FF00FF".

Enum

supports Int or String rawValue. It can be used only by adapting ConfigSerializable. If you want to use other enum, see Use custom value.

Decodable compliant type

read only

Codable compliant type

can set default value / read config value

Advanced Usage

You can easily get/set a value of custom type.
If you want to get/set ValueType (It’s a custom type that Lobster doesn’t support), you need to implement these steps:

Example case 1: Enum

// Adapt protocol `ConfigSerializable`
enum Status: ConfigSerializable {
    // Define `_config`, `_configArray`(If needed).
    // Custom ConfigBridge's definition see below.
    static var _config: ConfigBridge<Status> { return ConfigStatusBridge() }
    static var _configArray: ConfigBridge<[Status]> { fatalError("Not implemented") }

    case unknown
    case active
    case inactive

    init(value: String?) {
        guard let value = value else {
            self = .unknown
            return
        }
        switch value {
        case "active": self = .active
        case "inactive": self = .inactive
        default: self = .unknown
        }
    }

    var value: String {
        switch self {
        case .active: return "active"
        case .inactive: return "inactive"
        default: return ""
        }
    }
}

// Define Bridge class
final class ConfigStatusBridge: ConfigBridge<Status> {
    typealias T = Status

    // Save value to default store
    override func save(key: String, value: T?, defaultsStore: DefaultsStore) {
        defaultsStore[key] = value?.value
    }

    // Get value from RemoteConfig
    override func get(key: String, remoteConfig: RemoteConfig) -> T? {
        return remoteConfig[key].stringValue.flatMap(Status.init(value:))
    }

    // Get value from default store
    override func get(key: String, defaultsStore: DefaultsStore) -> T? {
        return (defaultsStore[key] as? String).flatMap(Status.init(value:))
    }
}

// Define ConfigKey
extension ConfigKeys {
    static let status = ConfigKey<Status>
}

// Set default
Lobster.shared[default: .status] = .inactive

// Use value
Lobster.shared.fetch { _ in
    let currentStatus = Lobster.shared[.status]
}

To define subscript makes it possible to access custom enum.

Example Case 2: Decodable compliant type

Just adapt Decodable or Codable to class or struct and adapt ConfigSerializable

struct Person: Codable, ConfigSerializable {
    let name: String
    let age: Int
    let country: String
}

extension ConfigKeys {
    static let person = CodableConfigKey<Person>("person")
}

Define config value like below in console:

Requirements

  • iOS 11.0+
  • Xcode 10+
  • Swift 5.0

Installation

CocoaPods

Lobster is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Lobster', '~> 3.1.0'

# If you want to use extensions of Combine, please install below:
pod 'Lobster/Combine'

and run pod install

Development

  • 1: setup project
$ cd path/to/Lobster
$ make
  • 2: prepare GoogleService-Info.plist

Due to security issues, I’m not providing GoogleService-Info.plist file. So please prepare it yourself in your Firebase Project.
And Please make Firebase App’s bundle identifier -.test.LobsterTests.
After that, put it into LobsterTests/.

Communication

  • If you found a bug, open an issue.
  • If you have a feature request, open an issue.
  • If you want to contribute, submit a pull request.:muscle:

License

Lobster is under MIT license. See the LICENSE file for more info.