Giter Club home page Giter Club logo

realflags's Introduction

logo-library

RealFlags makes it easy to configure feature flagsin your codebase. It's designed for Swift and provides a simple and elegant abstraction layer over multiple providers you can query with your priority. It also has a handy UI tool to browse and alter values directly at runtime!


Features | What You Get | Flags Browser & Editor | Tests | Documentation | Requirements | Installation | Powered Apps | Support & Contribute | License


Features Highlights

  • ๐Ÿ’ก Simple & Elegant: Effectively describe and organize your own flags with a type-safe structure. It will abstract your implementation and consistently reduce the amount of code to manage your feature flags
  • ๐Ÿ’ก Extensible: You can use one of the default data providers or create your own. We support Firebase Remote and Local configurations too
  • ๐Ÿ’ก Complete: Feature Flags support all primitive datatypes and complex objects: Int (and any numeric variant), String, Bool, Data, Date, URL, Dictionary, Array (values must conform to FlagProtocol), Optional Values and virtually any object conform to Codable protocol!
  • ๐Ÿ’ก Configurable: Enable, disable and customize features at runtime
  • ๐Ÿ’ก Integrated UI Tool: the handy UI Tool allows you to customize and read flags at a glance

An Hands-on Approach

Would you use RealFlags in your product? We wrote an article about it. Check it out here

What You Get

Our goal in making RealFlags is to provide a type-safe abstract way to describe and query for feature flags.

The first step is to describe your collection of flags:

// Define the structure of your feature flags with type-safe properties!
struct UserFlags: FlagCollectionProtocol {
 @Flag(default: true, description: "Show social login options along native login form")
 var showSocialLogin: Bool
 
 @Flag(default: 0, description: "Maximum login attempts before blocking account")
 var maxLoginAttempts: Int
 
 @Flag(key: "rating_mode", default: "at_launch", description: "The behaviour to show the rating popup")
 var appReviewRating: String

 public init() {}
}

This represents a type-safe description of some flags grouped in a collection. Each feature flags property is identified by the @Flag annotation. It's time load values for this collection; using a new FlagsLoader you will be able to load and query collection's values from one or more data providers:

// Allocate your own data providers
let localProvider = LocalProvider(localURL: fileURL)
let fbProvider = FirebaseRemoteProvider()

// Loader is the point for query values
let userFlagsLoader = FlagsLoader(UserFlags.self, // load flags definition
 description: .init(name: "User Features", description: "Cool experimental features for user account"),
 providers: [fbProvider, localProvider]) // set providers

Now you can query values from userFlagsLoader by using the UserFlags structure (it supports autocomplete and type-safe values thanks to @dynamicMemberLookup!). Let me show it:

if userFlagsLoader.showSocialLogin { // query properties as type-safe with autocomplete!
 // do some stuff
}

Values are obtained respecting the order you have specified, in this case from Firebase Remote Config service, then, when no value is found, into the local repository. If no values are available, the default value specified in @Flags annotation is returned.

This is just an overview of the library; if you want to know more, follow the documentation below.

Flags Browser & Editor

RealFlags also comes with a handy tool you can use to browse and edit feature flag values directly in your client! It can be helpful for testing purposes or allow product owners to enable/disable and verify features of the app.

Checkout the doc for more infos!

flags_browser_video.mp4

API Reference

RealFlags is fully documented at the source-code level. You'll get autocomplete with doc inside XCode for free; moreover, you can read the full Apple's DoCC Documentation automatically generated thanks to Swift Package Index Project from here:

๐Ÿ‘‰ API REFERENCE

Documentation

The following documentation describes detailed usage of the library.

Test Suite

RealFlags includes an extensive collection of unit tests: you can find it in the Tests directory.

Requirements

RealFlags can be installed on any platform which supports Swift 5.4+, including Windows and Linux. On Apple platform, the following configuration is required:

  • iOS 12+
  • Xcode 12.5+
  • Swift 5.4+

Installation

To use RealFlags in your project you can use Swift Package Manager (our primary choice) or CocoaPods.

Swift Package Manager

Add it as a dependency in a Swift Package, and add it to your Package.swift:

dependencies: [
 .package(url: "https://github.com/immobiliare/RealFlags.git", from: "1.0.0")
]

And add it as a dependency of your target:

targets: [
 .target(name: "MyTarget", dependencies: [
 .product(name: "https://github.com/immobiliare/RealFlags.git", package: "RealFlags")
 ])
]

In Xcode 11+, you can also navigate to the File menu and choose Swift Packages -> Add Package Dependency..., then enter the repository URL and version details.

CocoaPods

RealFlags can be installed with CocoaPods by adding pod 'RealFlags' to your Podfile.

pod 'RealFlags' // to import the core framework
pod 'RealFlagsFirebase' // to also import Firebase Data Provider

Powered Apps

The amazing mobile team at ImmobiliareLabs created RealFlags, the Tech dept at Immobiliare.it, the first real estate site in Italy. We are currently using RealFlags in all of our products.

If you are using RealFlags in your app drop us a message.

Support

ImmobiliareLabs

We'd love for you to contribute to RealFlags! If you have questions about using RealFlags, bugs, and enhancement, please feel free to reach out by opening a GitHub Issue.

License

RealFlags is licensed under the MIT license. See the LICENSE file for more information.

realflags's People

Contributors

antranapp avatar danielcardonarojas avatar ipodishima avatar jellybellydev avatar malcommac avatar nicfontana avatar tr736-reclip avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

realflags's Issues

[Bug]: instance! crashes sometimes

Platform Version

iOS 16

SDK Version

3.5

Xcode Version

Xcode 14.3

Steps To Reproduce

Race condition, just try to call getValueForFlag right after instantiating

Expected Behavior

The entire app should not crash. Probably better not to use ! at all

Actual Incorrect Behavior

The app crashes occasionally. I put the getValueForFlag in a Task so it gets delayed, which seems to help.

A Sample App would be nice

Feature Request

Q A
New Feature yes
RFC no
BC Break no

Summary

First of all, thank you for the great library. It looks modern and easy to use.

I'd suggest to add a simple demo app into this library, so that other can easily start play around with the library immediately without initial setup.

Computed value for annotated feature flag

Feature Request

Sometimes you may need to define a feature flag where the value is computed dynamically.
For example suppose you have a feature flag called hasPublishButton: it should return true only when the app's language is set to it.

Q A
New Feature yes
RFC yes/no
BC Break yes

Summary

In order to provide support for this feature request we have implemented computedValue in @Flag property; it defines a function which is called before asking for value to any provider or return the default fallback value.
If the function return a non nil value the value is returned and no further check are made.
If you return a nil value the flow still the same; flag value are asked to the ordered list of providers (following the optional exclusion schema) and eventually by returning the default local value set.

This is an example:

public struct MiscFlags: FlagCollectionProtocol {

    // MARK: - Flags

    @Flag(default: false, computedValue: MiscFlags.computedPublishButton, description: "")
    var hasPublishButton: Bool
            
    // MARK: - Computed Properties Functions

    public init() { }

    private static func computedPublishButton() -> Bool? {
        Language.main.code == "it"
    }
}

As you can see we have defined a private static function computedPublishButton() in order to avoid making the @Flag annotation full of bloat code; this because generally this kind of check still short but sometimes it may be more complex.

Bundle for FlagsControllerBrowser's storyboard failed to instantiate via CocoaPods

Bug Report

Bundle is currently loaded by getting Bundle(for: Self.self) in extension of Bundle inside the package.
However Bundle's path is set to:

<AppName>.app/Frameworks/RealFlags.framework/RealFlags

but now the bundle is inside RealFlags.bundle.

How to reproduce

Using the package via CocoaPods and instantiate a new controller via the default create() of the controller.

Fix

Allows the bundle function to search inside the package itself if generated:

extension Bundle {
    
    private static let internalBundle = Bundle(for: FlagsBrowserController.self)
    
    internal static var libraryBundle: Bundle {
        #if SWIFT_PACKAGE
        Bundle.module
        #else
        [
            Bundle(url: internalBundle.bundleURL.appendingPathComponent("RealFlags.bundle")),
            internalBundle
        ].lazy.compactMap({ $0 }).first ?? Bundle.main
        #endif
    }
    
}

LocalProvider fail to throw error if something went wrong saving local flags

Bug Report

LocalProvider class fails to throw errors when saving internal flags dictionary inside a local file making the error catching really difficult.

Q A
BC Break yes
Version 1.1.0

How to reproduce

Just allocate a new LocalProvider pointing to an URL of a directory instead of a file.

Expected behavior

public func setValue<Value>(_ value: Value?, forFlag key: FlagKeyPath) throws -> Bool where Value: FlagProtocol support throws but the saveToDisk() function does not throw, instead it uses optional throw via try?.

Just remove it to fix the issue.

Search support for feature flags in Browser Controller

Feature Request

We should be able to search for a feature flag inside the flags browser controller. It could be useful when you have lots of different feature flags inside a collection.

Q A
New Feature no
BC Break /no

Reset values for LocalProvider and per flag in every writable provider

Feature Request

Allows reset of values inside a collection both via call and inside the browser of flags.
This feature should be also implemented to reset a single flag.
The scope is to reset any writable provider (ie. for LocalProvider it will remove the dictionary serialized file) and so on.

Q A
New Feature yes
BC Break no

Better presentation for optional data inside the flags browser

Feature Request

At this time all the optional data is showed using Optional(Value) string inside the browser. This is just the debug description form coming from the Swift runtime. We should optimize it by showing the data if available eventually with a small icon to inform about the optional nature.

Q A
New Feature yes

Make the Flags Browser more user friendly for general purpose users

Feature Request

While it's more complete, especially for debug purpose, the flags browser interface is far from being simple.
Especially if you want to expose it to your non-tech managers the resulting experience is really poor.

We'll work on the following issues:

  • Simplify the initial flags collection list showing only the relevant informations (without describing the type of collection but exposing a metadata section event for FlagsCollection type).
  • Allowing in-place toggle for the most common used data type: the boolean. When you open a flags collection you should be able to change the value of boolean flags directly from list. This should works only if flag has one or more writable providers (the toggle operation will affects all the writable data providers like the LocalProvider).
  • Show relevant errors when set the value of a flag
Q A
New Feature yes
BC Break no

The following screenshots describe the expected result:

Screenshot 2022-01-22 at 13 19 21

Screenshot 2022-01-22 at 13 19 16

FlagProvider's flagValue(from:fallback) function ignores fallback setting

Feature Request

By calling flagValue(from:fallback) function from any FlagLoader concrete implementation value obtained ignores the fallback parameter which means "return the default value if nothing found on any provider".
Fallback is always returned when loader's reference is not available, while in case everything is setup correctly and no values were found the returned value is nil instead of defaultValue.

Q A
New Feature no
BC Break yes

wrappedTypeFromOptionalType() function in FirebaseRemoteProvider works only with optional type, fails with non-optional

Bug Report

FirebaseRemoteProvider's wrappedTypeFromOptionalType() function does not work correctly with non optional type and fails returning nil values even for available flags. This causes wrong behaviour by querying remote provider for set values.

Fix

Fix includes fallback value for wrappedTypeFromOptionalType which allows us to return the unwrapped value correctly:

public func wrappedTypeFromOptionalType(_ type: Any.Type) -> Any.Type? {
    (type as? OptionalProtocol.Type)?.wrappedType ?? type
}

Give the opportunity to alter the default value of a Flag

Feature Request

Give the opportunity to alter the default value of a Flag.
Sometimes you may need to alter the default value specified inside the property wrapper signature. This happens if you want to define a single collection file used in several targets of your project where one or more flags have different default values.
In this case you can avoid to replicate the same collection blueprint but just change the default value.

A new call setDefaultValue() is therefore provided to allows this change.

Q A
New Feature yes
BC Break no

Providers not respecting order defined

Bug Report

Q A
BC Break no
Version 1.3.1

Summary

I'm setting providers: [FeatureFlagLocalProvider(), FirebaseRemoteProvider()]. for this property

default: True
localProvider: True
firebase: False

Current behavior

The flag resolves as false, somehow deciding to choose firebase over the localProvider

How to reproduce

unsure, it seems like it should just work if you do [localProvider, firebaseProvider]

Expected behavior

the flag should resolve as True, respecting the local provider over the firebase provider

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.