{"id":16684464,"url":"https://github.com/sgr-ksmt/lobster","last_synced_at":"2025-07-18T17:33:51.164Z","repository":{"id":26703844,"uuid":"109006173","full_name":"sgr-ksmt/Lobster","owner":"sgr-ksmt","description":"🦐 Type-safe Firebase-RemoteConfig helper library 🦐","archived":false,"fork":false,"pushed_at":"2022-07-22T00:59:34.000Z","size":1018,"stargazers_count":77,"open_issues_count":6,"forks_count":6,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-05-02T02:05:43.403Z","etag":null,"topics":["ab-testing","cocoapods","firebase","ios","remote-config","swift","type-safe"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sgr-ksmt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"sgr-ksmt"}},"created_at":"2017-10-31T14:19:10.000Z","updated_at":"2024-03-18T12:45:21.000Z","dependencies_parsed_at":"2022-08-09T03:30:22.665Z","dependency_job_id":null,"html_url":"https://github.com/sgr-ksmt/Lobster","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgr-ksmt%2FLobster","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgr-ksmt%2FLobster/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgr-ksmt%2FLobster/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sgr-ksmt%2FLobster/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sgr-ksmt","download_url":"https://codeload.github.com/sgr-ksmt/Lobster/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243835942,"owners_count":20355611,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ab-testing","cocoapods","firebase","ios","remote-config","swift","type-safe"],"created_at":"2024-10-12T14:43:58.269Z","updated_at":"2025-03-17T00:32:51.795Z","avatar_url":"https://github.com/sgr-ksmt.png","language":"Swift","funding_links":["https://github.com/sponsors/sgr-ksmt"],"categories":[],"sub_categories":[],"readme":"# Lobster\n\nType-safe Firebase-RemoteConfig helper library.\n\n\n[![GitHub release](https://img.shields.io/github/release/sgr-ksmt/Lobster.svg?style=for-the-badge)](https://github.com/sgr-ksmt/Lobster/releases)\n![Language](https://img.shields.io/badge/language-Swift%205.0-orange.svg?style=for-the-badge)  \n\n[![Carthage Compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=for-the-badge)](https://github.com/Carthage/Carthage)\n[![CocoaPods](https://img.shields.io/badge/Cocoa%20Pods-compatible-4BC51D.svg?style=for-the-badge)](https://cocoapods.org/pods/Lobster)\n\n## Feature\n\n- Can get a value from RemoteConfig / set a value to RemoteConfig to type-safe.\n- Easy to set default value to RemoteConfig by using key-value subscripting.\n- Custom type available ✨\n  - `String`/`Int` enum\n  - `Decodable`(read-only) and `Codable`.\n- Can manage expiration duration of config values.\n- Combine framwork support.\n\n---\n\n## Getting Started\n\n- [API Documentation](https://sgr-ksmt.github.io/Lobster/index.html)\n- Example Apps\n  - [Swift Demo](https://github.com/sgr-ksmt/Lobster/tree/master/Demo)\n  - [SwiftUI + Combine Demo](https://github.com/sgr-ksmt/Lobster/tree/master/SwiftUI-Demo)\n\n## Basic Usage\n\n**You can integrate Lobster in a few steps implementation:**\n\n### 1. Define `ConfigKey`\n\n```swift\nextension ConfigKeys {\n    static let welcomeTitle = ConfigKey\u003cString\u003e(\"welcome_title\")\n    static let welcomeTitleColor = ConfigKey\u003cUIColor\u003e(\"welcome_title_color\")\n}\n```\n\n### 2. Register value to Firebase Project\n\n[Go to Firebase Project](https://console.firebase.google.com/) and set values you want to get.\n\n![](readme-docs/img1.png)\n\n\n### 3. Let's use Lobster\n\n```swift\nimport Lobster\n\n// Set default value\nLobster.shared[default: .welcomeTitle] = \"Welcome\"\nLobster.shared[default: .welcomeTitleColor] = .black\n\nself.titleLabel.text = Lobster.shared[.welcomeTitle]\n\n// Fetch remote-config\nLobster.shared.fetch { _ in\n    dispatchQueue.main.async { [weak self] in\n        self?.titleLabel.text = Lobster.shared[.welcomeTitle]\n        self?.titleLabel.textColor = Lobster.shared[.welcomeTitleColor]\n    }\n}\n```\n\n## Tips for you\n\n### Combine\n\nYou can get values from Lobster with Combine's stream.  \nHere is a sampl viewmodel class.\n\n```swift\nimport Lobster\nimport Combine\n\nextension ConfigKeys {\n    static let title = ConfigKey\u003cString\u003e(\"title\")\n}\n\nfinal class ViewModel: ObservableObject {\n    @Published var title: String\n    private var cancellables: Set\u003cAnyCancellable\u003e = []\n\n    init() {\n        title = Lobster.shared[.titleText]\n\n        Lobster.shared.combine.fetched(.title)\n            .receive(on: RunLoop.main)\n            .assign(to: \\.title, on: self)\n            .store(in: \u0026cancellables)\n    }\n}\n```\n\nNOTE: You need to install `Lobster/Combine` before using it.\n\n### Get value with subscripting syntax.\n\nUse subscripting syntax.\n\n- Non-Optional\n\n```swift\nextension ConfigKeys {\n    static let text = ConfigKey\u003cString\u003e(\"text\")\n}\n\n// Get value from config.\n// If value didn't fetch from remote yet. returns default value (if exists).\nlet text: String = Lobster.shared[.text]\n\n// Get value from only config.\n// it is possible to crash if value didn't fetch from remote yet.\nlet text: String = Lobster.shared[config: .text]\n\n// Get value from only default.\n// It is possible to crash if the default value is not set yet.\nlet text: String = Lobster.shared[default: .text]\n\n// [safe:], [safeConfig:], [safeDefault:] subscripting syntax.\n// It is safe because they return nil if they have no value.(return type is `Optional\u003cT\u003e`.)\nlet text: String? = Lobster.shared[safe: .text]\nlet text: String? = Lobster.shared[safeConfig: .text]\nlet text: String? = Lobster.shared[safeDefault: .text]\n```\n\n- Optional\n\n```swift\nextension ConfigKeys {\n    static let textOptional = ConfigKey\u003cString?\u003e(\"text_optional\")\n}\n\nlet text: String? = Lobster.shared[.textOptional]\nlet text: String? = Lobster.shared[config: .textOptional]\nlet text: String? = Lobster.shared[default: .textOptional]\n```\n\n### Set Default value\n\nYou can set default values using `subscripting syntax` or plist.\n\n```swift\n// Set default value using `[default:]` syntax.\nLobster.shared[default: .titleText] = \"Cart Items\"\nLobster.shared[default: .titleColor] = .black\n\n// or load from `defaults.plist`\nLobster.shared.setDefaults(fromPlist: \"defaults\")\n```\n\n### Set debug mode\n\n```swift\n// Enable debug mode (development only)\nLobster.shared.debugMode = true\nLobster.shared.fetchExpirationDuration = 0.0\n```\n\n### isStaled\n\nIf you set `isStaled` to true, Lobster will fetch remote value ignoring `fetchExpirationDuration`.  \nThat is, You can retrieve config values immediately when you call `fetch` after You set `isStales` to true.  \nAnd `isStaled` will be set to `false` after fetched remote value.\n\n```swift\nLobster.shared.fetchExpirationDuration = 60 * 12\n\nLobster.shared.isStaled = true\n\n// Default expire duration is 12 hours.\n// But if `isStaled` set to true,\n// Lobster fetch values from remote ignoring expire duration.\nLobster.shared.fetch()\n```\n\n## Supported types\n\nLobster supports more types as default followings:\n\n- String\n- Int\n- Float\n- Double\n- Bool\n- Data\n- URL\n- UIColor\n- enum(String/Int)\n- Decodable Object\n- Codable Object\n- Collection(Array)\n  - String\n  - Int\n  - Float\n  - Double\n  - Bool\n  - Data\n  - URL\n  - enum(String/Int)\n  - Decodable Object\n  - Codable Object\n\n### TODO\n\n- [ ] CGPoint\n- [ ] CGSize\n- [ ] CGRect\n- [ ] Dictionary\n\n#### URL\n\nSupports text: \n\n![](readme-docs/img2.png)\n\n#### UIColor\n\nSupports only HEX string like `\"#FF00FF\"`.\n\n![](readme-docs/img3.png)\n\n#### Enum\n\nsupports `Int` or `String` rawValue.\nIt can be used only by adapting `ConfigSerializable`.\nIf you want to use other enum, see ***Use custom value***.\n\n#### Decodable compliant type\n\nread only\n\n#### Codable compliant type\n\ncan set default value / read config value\n\n\n## Advanced Usage\n\nYou can easily get/set a value of custom type.  \nIf you want to get/set `ValueType` (It's a custom type that Lobster doesn't support), you need to implement these steps:\n\n- Conform `ConfigSerializable` to the `ValueType`\n- Create `ConfigBridge\u003cValueType\u003e\n- Define `ConfigKey\u003cValueType\u003e`\n\n### Example case 1: Enum\n\n```swift\n// Adapt protocol `ConfigSerializable`\nenum Status: ConfigSerializable {\n    // Define `_config`, `_configArray`(If needed).\n    // Custom ConfigBridge's definition see below.\n    static var _config: ConfigBridge\u003cStatus\u003e { return ConfigStatusBridge() }\n    static var _configArray: ConfigBridge\u003c[Status]\u003e { fatalError(\"Not implemented\") }\n\n    case unknown\n    case active\n    case inactive\n\n    init(value: String?) {\n        guard let value = value else {\n            self = .unknown\n            return\n        }\n        switch value {\n        case \"active\": self = .active\n        case \"inactive\": self = .inactive\n        default: self = .unknown\n        }\n    }\n\n    var value: String {\n        switch self {\n        case .active: return \"active\"\n        case .inactive: return \"inactive\"\n        default: return \"\"\n        }\n    }\n}\n\n// Define Bridge class\nfinal class ConfigStatusBridge: ConfigBridge\u003cStatus\u003e {\n    typealias T = Status\n\n    // Save value to default store\n    override func save(key: String, value: T?, defaultsStore: DefaultsStore) {\n        defaultsStore[key] = value?.value\n    }\n\n    // Get value from RemoteConfig\n    override func get(key: String, remoteConfig: RemoteConfig) -\u003e T? {\n        return remoteConfig[key].stringValue.flatMap(Status.init(value:))\n    }\n\n    // Get value from default store\n    override func get(key: String, defaultsStore: DefaultsStore) -\u003e T? {\n        return (defaultsStore[key] as? String).flatMap(Status.init(value:))\n    }\n}\n\n// Define ConfigKey\nextension ConfigKeys {\n    static let status = ConfigKey\u003cStatus\u003e\n}\n\n// Set default\nLobster.shared[default: .status] = .inactive\n\n// Use value\nLobster.shared.fetch { _ in\n    let currentStatus = Lobster.shared[.status]\n}\n```\n\nTo define subscript makes it possible to access custom enum.\n\n### Example Case 2: Decodable compliant type\n\nJust adapt `Decodable` or `Codable` to class or struct and adapt `ConfigSerializable`\n\n```swift\nstruct Person: Codable, ConfigSerializable {\n    let name: String\n    let age: Int\n    let country: String\n}\n\nextension ConfigKeys {\n    static let person = CodableConfigKey\u003cPerson\u003e(\"person\")\n}\n```\n\nDefine config value like below in console:\n\n![](readme-docs/img5.png)\n\n\n## Requirements\n- iOS 11.0+\n- Xcode 10+\n- Swift 5.0\n\n## Installation\n\n### [CocoaPods](https://cocoapods.org/)\n\n**Lobster** is available through [CocoaPods](http://cocoapods.org). To install\nit, simply add the following line to your Podfile:\n\n```ruby\npod 'Lobster', '~\u003e 3.1.0'\n\n# If you want to use extensions of Combine, please install below:\npod 'Lobster/Combine'\n```\n\nand run `pod install`\n\n## Development\n\n- 1: setup project\n\n```bash\n$ cd path/to/Lobster\n$ make\n```\n\n- 2: prepare `GoogleService-Info.plist`\n\nDue to security issues, I'm not providing `GoogleService-Info.plist` file.\nSo please prepare it yourself in your Firebase Project.  \nAnd Please make Firebase App's bundle identifier `-.test.LobsterTests`.  \nAfter that, put it into `LobsterTests/`.\n\n\n\u003cimg src=\"readme-docs/img6.png\" width=\"500\" /\u003e\n\n## Communication\n- If you found a bug, open an issue.\n- If you have a feature request, open an issue.\n- If you want to contribute, submit a pull request.:muscle:\n\n## License\n**Lobster** is under MIT license. See the [LICENSE](LICENSE) file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsgr-ksmt%2Flobster","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsgr-ksmt%2Flobster","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsgr-ksmt%2Flobster/lists"}