• This repository has been archived on 05/Jan/2024
  • Stars
    star
    203
  • Rank 192,853 (Top 4 %)
  • Language
    Swift
  • License
    Apache License 2.0
  • Created over 7 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

JustTweak is a feature flagging framework for iOS apps.

JustTweak Banner

JustTweak

Build Status Version License Platform

JustTweak is a feature flagging framework for iOS apps. It provides a simple facade interface interacting with multiple tweak providers that are queried respecting a given priority. Tweaks represent flags used to drive decisions in the client code.

With JustTweak you can achieve the following:

  • use a JSON file to provide the default values for feature flagging
  • use a number of remote tweak providers such as Firebase and Optmizely to run A/B tests and feature flagging
  • enable, disable, and customize features locally at runtime
  • provide a dedicated UI for customization (this comes particularly handy for feature that are under development to showcase it to stakeholders)

Installation

CocoaPods

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

pod "JustTweak"

Swift Package Manager

JustTweak is also available through SPM. Copy the URL for this repo, and add the package in your project settings.

Implementation

Integration

  • Define a LocalTweakProvider JSON file including your features. Refer to LocalTweaks_example.json for a starting point.
  • Configure the stack

To configure the stack, you have two options:

  • implement the stack manually
  • leverage the code generator tool

Manual integration

  • Configure the JustTweak stack as following:
static let tweakManager: TweakManager = {
    var tweakProviders: [TweakProvider] = []

    // Mutable TweakProvider (to override tweaks from other TweakProviders)
    let userDefaultsTweakProvider = UserDefaultsTweakProvider(userDefaults: UserDefaults.standard)
    tweakProviders.append(userDefaultsTweakProvider)
    
    // Optimizely (remote TweakProvider)
    let optimizelyTweakProvider = OptimizelyTweakProvider()
    optimizelyTweakProvider.userId = UUID().uuidString
    tweakProviders.append(optimizelyTweakProvider)

    // Firebase Remote Config (remote TweakProvider)
    let firebaseTweakProvider = FirebaseTweakProvider()
    tweakProviders.append(firebaseTweakProvider)

    // Local JSON-based TweakProvider (default TweakProvider)
    let jsonFileURL = Bundle.main.url(forResource: "LocalTweaks_example", withExtension: "json")!
    let localTweakProvider = LocalTweakProvider(jsonURL: jsonFileURL)
    tweakProviders.append(localTweakProvider)
    
    return TweakManager(tweakProviders: tweakProviders)
}()
  • Implement the properties and constants for your features, backed by the LocalTweakProvider. Refer to TweakAccessor.swift for a starting point.

Using the code generator tool

  • Define the stack configuration in a config.json file in the following format:
{
    "accessorName": "GeneratedTweakAccessor"
}

the only currently supported value is accessorName that defines the name of the generated class.

  • Add the following to your Podfile
script_phase :name => 'TweakAccessorGenerator',
             :script => '$SRCROOT/../TweakAccessorGenerator \
             -l $SRCROOT/<path_to_the_local_tweaks_json_file> \
             -o $SRCROOT/<path_to_the_output_folder_for_the_generated_code> \
             -c $SRCROOT/<path_to_the_folder_containing_config.json>',
             :execution_position => :before_compile

Every time the target is built, the code generator tool will regenerate the code for the stack. It will include all the properties backing the features defined in the LocalTweakProvider.

  • Add the generated files to you project and start using the stack.

Usage

Basic

If you have used the code generator tool, the generated stack includes all the feature flags. Simply allocate the accessor object (which name you have defined in the .json configuration and use it to access the feature flags.

let accessor = GeneratedTweakAccessor(with: <#tweak_manager_instance#>)
if accessor.meaningOfLife == 42 {
    ...
}

See GeneratedTweakAccessor.swift and GeneratedTweakAccessor+Constants.swift for an example of generated code.

Advanced

If you decided to implement the stack code yourself, you'll have to implemented code for accessing the features via the TweakManager.

The three main features of JustTweak can be accessed from the TweakManager instance to drive code path decisions.

  1. Checking if a feature is enabled
// check for a feature to be enabled
let enabled = tweakManager.isFeatureEnabled("some_feature")
if enabled {
    // enable the feature
} else {
    // default behaviour
}
  1. Get the value of a flag for a given feature. TweakManager will return the value from the tweak provider with the highest priority and automatically fallback to the others if no set value is found. It throws exeception when a nil Tweak is found which can be catched and handled as needed.

Use either tweakWith(feature:variable:) or the provided property wrappers.

// check for a tweak value
let tweak = try? tweakManager.tweakWith(feature: "some_feature", variable: "some_flag")
if let tweak = tweak {
    // tweak was found in some tweak provider, use tweak.value
} else {
    // tweak was not found in any tweak provider
}

Or with do-catch

// check for a tweak value
do {
    let tweak = try tweakManager.tweakWith(feature: "some_feature", variable: "some_flag")
    // tweak was found in some tweak provider, use tweak.value
    return tweak    
} catch let error as TweakError {
    switch error {
    case .notFound: () // "Feature or variable is not found"
    case .notSupported: () // "Variable type is not supported"
    case .decryptionClosureNotProvided: () // "Value is encrypted but there's no decryption closure provided"
    }
} catch let error { // add a default catch to satisfy the compiler
    print(error.localizedDescription)
}

@TweakProperty, @OptionalTweakProperty and @FallbackTweakProperty property wrappers are available to mark properties representing feature flags. Mind that in order to use these property wrappers, a static instance of TweakManager is needed.

@TweakProperty(feature: <#feature_key#>,
               variable: <#variable_key#>,
               tweakManager: <#TweakManager#>)
var labelText: String
@OptionalTweakProperty(fallbackValue: <#nillable_fallback_value#>,
                       feature: <#feature_key#>,
                       variable: <#variable_key#>,
                       tweakManager: <#TweakManager#>)
var meaningOfLife: Int?
@FallbackTweakProperty(fallbackValue: <#nillable_fallback_value#>,
                       feature: <#feature_key#>,
                       variable: <#variable_key#>,
                       tweakManager: <#TweakManager#>)
var shouldShowFeatureX: Bool

Tweak Providers priority

The order of the objects in the tweakProviders array defines the priority of the tweak providers.

The MutableTweakProvider with the highest priority, such as UserDefaultsTweakProvider in the example above, will be used to reflect the changes made in the UI (TweakViewController). The LocalTweakProvider should have the lowest priority as it provides the default values from a local tweak provider and it's the one used by the TweakViewController to populate the UI.

Migration notes

In order to migrate from manual to the code generated implementation, it is necessary to update to the new .json format. To aid with this process we have added the GeneratedPropertyName property to the tweak object. Set this value to align with your current property names in code, so that the generated accessor properties match your existing implementation.

Caching notes

The TweakManager provides the option to cache the tweak values in order to improve performance. Caching is disabled by default but can be enabled via the useCache property. When enabled, there are two ways to reset the cache:

  • call the resetCache method on the TweakManager
  • post a TweakProviderDidChangeNotification notification

Update a mutable tweak provider at runtime

JustTweak comes with a ViewController that allows the user to edit the MutableTweakProvider with the highest priority.

func presentTweakViewController() {
    let tweakViewController = TweakViewController(style: .grouped, tweakManager: <#TweakManager#>)

    // either present it modally
    let tweaksNavigationController = UINavigationController(rootViewController:tweakViewController)
    tweaksNavigationController.navigationBar.prefersLargeTitles = true
    present(tweaksNavigationController, animated: true, completion: nil)

    // or push it on an existing UINavigationController
    navigationController?.pushViewController(tweakViewController, animated: true)
}

When a value is modified in any MutableTweakProvider, a notification is fired to give the clients the opportunity to react and reflect changes in the UI.

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.defaultCenter().addObserver(self,
                                                   selector: #selector(updateUI),
                                                   name: TweakProviderDidChangeNotification,
                                                   object: nil)
}

@objc func updateUI() {
    // update the UI accordingly
}

Customization

JustTweak comes with three tweak providers out-of-the-box:

  • UserDefaultsTweakProvider which is mutable and uses UserDefaults as a key/value store
  • LocalTweakProvider which is read-only and uses a JSON file that is meant to hold the default feature flagging setup
  • EphemeralTweakProvider which is simply an instance of NSMutableDictionary

In addition, JustTweak defines TweakProvider and MutableTweakProvider protocols you can implement to create your own tweak provider to fit your needs. In the example project you can find some examples which you can use as a starting point.

Encryption or pre-processing (Advanced)

JustTweak offers the ability to add a decryptionClosure to a TweakProvider. This closure takes the Tweak as input and returns a TweakValue as output. The closure allows you to do some preprocessing on your tweak which can e.g. be used to decrypt values. This can be used if you have an encrypted value in your tweaks JSON file as can be seen below:

"encrypted_answer_to_the_universe": {
  "Title": "Encrypted definitive answer",
  "Description": "Encrypted answer to the Ultimate Question of Life, the Universe, and Everything",
  "Group": "General",
  "Value": "24 ton yletinifeD",
  "GeneratedPropertyName": "definitiveAnswerEncrypted",
  "Encrypted": true
}

Note that you have to specify if the value is encrypted in your JSON file (with the Encrypted property) for the decryption closure to process the value. The decryption closure for the JSON above can be specified as follows:

tweakProvider.decryptionClosure = { tweak in
    // decrypt `tweak.value` with your cypher of choice and return the decrypted value
}

In this way, the tweak fetched from the tweak provider will have the decrypted value.

License

JustTweak is available under the Apache 2.0 license. See the LICENSE file for more info.

  • Just Eat iOS team

More Repositories

1

ScrollingStackViewController

A view controller that uses root views of child view controllers as views in a UIStackView.
Swift
646
star
2

JustLog

JustLog brings logging on iOS to the next level. It supports console, file and remote Logstash logging via TCP socket with no effort. Support for logz.io available.
Swift
518
star
3

httpclient-interception

A .NET library for intercepting server-side HTTP requests
C#
246
star
4

JustPersist

JustPersist is the easiest and safest way to do persistence on iOS with Core Data support out of the box. It also allows you to migrate to any other persistence framework with minimal effort.
Swift
165
star
5

NavigationEngineDemo

A scalable and robust solution to navigation, deep linking and universal links on iOS 🚀
Swift
125
star
6

Shock

A HTTP mocking framework written in Swift.
Swift
101
star
7

JustPeek

JustPeek is an iOS Library that adds support for Force Touch-like Peek and Pop interactions on devices that do not natively support this kind of interaction.
Shell
68
star
8

Topshelf.Nancy

Nancy endpoint for the Topshelf service host providing additional support around URL reservations. — Edit
C#
65
star
9

ZendeskApiClient

C# Client for working with the Zendesk API
C#
64
star
10

JustEat.RecruitmentTest

The recruitment test to apply for an engineering role at Just Eat
Scala
62
star
11

AutomationTools

iOS UI testing framework and guidelines
Swift
52
star
12

NLog.StructuredLogging.Json

Structured logging for NLog using Json (formerly known as JsonFields)
C#
51
star
13

JustTrack

The Just Eat solution to better manage the analytics tracking and improve the relationship with your BI team.
Swift
42
star
14

JustFakeIt

HTTP server faking library
C#
31
star
15

jubako

A small API to help display rich content in a RecyclerView such as a wall of carousels
Kotlin
30
star
16

JustSaying

A light-weight message bus on top of AWS services (SNS and SQS).
C#
30
star
17

JustBehave

A BDD-influenced C# testing library cooked up by Just Eat
C#
29
star
18

PRAssigner

Swift AWS Lambda to automatically assign engineers to pull requests with a Slack integration
Swift
29
star
19

ApplePayJSSample

A sample implementation of Apple Pay JS using ASP.NET Core
C#
23
star
20

kongverge

A desired state configuration tool for Kong
C#
23
star
21

ProcessManager

C#
21
star
22

Android.Samples.Deeplinks

Sample app demonstrating an effective and unit-testable way to handle deep links in Android
Java
20
star
23

JustEat.StatsD

Our library for publishing metrics to statsd
C#
19
star
24

iOS.ErrorUtilities

Just Eat collection of iOS error-related utilities
Swift
19
star
25

JustSupport

JavaScript
18
star
26

AwsWatchman

Because unmonitored infrastructure will bite you
C#
17
star
27

fozzie-components

JavaScript
13
star
28

kubernetes-windows-aws-ovs

Kubernetes on windows in aws using ovn
HCL
13
star
29

applepayjs-polyfill

A polyfill for the Apple Pay JS for use in non-supported browsers
JavaScript
12
star
30

fozzie

Web UI Base Library
SCSS
12
star
31

LocalDynamoDb

C#
11
star
32

JE.IdentityServer.Security

Recaptcha for OpenIdConnect
C#
7
star
33

JustEat.Recruitment.UI

UI Test for interview candidates
7
star
34

OpenRastaSwagger

Swagger / Swagger-UI implementation for OpenRasta
JavaScript
7
star
35

ts-jsonschema-builder

Fluent TypeScript JSON Schema builder.
TypeScript
6
star
36

JustEat.InfoSecRecruitmentTest

C#
4
star
37

gulp-build-fozzie

Gulp build tasks for use across Fozzie modules
JavaScript
4
star
38

JustEat.CWA.RecruitmentTest

Consumer Web Applications recruitment test for roles at JUST EAT
4
star
39

AngularJSWebAPI.Experiment

HTML
3
star
40

JE.EmbeddedChecks

Micro-framework for embedding checks inside applications, so they tell you when they're healthy and how.
C#
3
star
41

JE.TurningPages

C#
3
star
42

JE.ApiValidation

API validation helpers - A standard error response contract and FluentValidation support in .NET web frameworks
C#
3
star
43

JE.ApiExceptions

C#
2
star
44

f-footer

Common footer component for Just Eat websites
SCSS
2
star
45

JustEat.EntryLevel.RecruitmentTest

Recruitment Test for Entry Level Engineers
2
star
46

global-component-library

Global Component Library and Documentation for sharing across UK and International
CSS
2
star
47

f-templates

Locate, compile, and serve HTML from templates.
JavaScript
1
star
48

f-dom

Fozzie JS DOM queries library.
JavaScript
1
star
49

f-toggle

Fozzie vanilla JS toggle library.
JavaScript
1
star
50

PowerShellDSCUtils

Just Eat PowerShell DSC Utilities
PowerShell
1
star
51

f-header

Common header component for Just Eat websites
JavaScript
1
star
52

f-recruit-message

Adds a recruitment message to the browser console
JavaScript
1
star
53

ruby_bootcamp

Code samples for ruby bootcamp show & tells
Ruby
1
star
54

f-serviceworker

JavaScript
1
star
55

mickeydb

Android sqlite db super tool, for migrations and data access code generation
Java
1
star
56

f-copy-assets

NPM package to copy assets from node_modules to a specified directory
JavaScript
1
star
57

JustEat.PublicApi.TestApp

JavaScript
1
star
58

browserslist-config-fozzie

Just Eat's Browserslist Config used in UI packages
JavaScript
1
star
59

openrasta-hosting-owin

OWIN host for OpenRasta
C#
1
star