{"id":44947436,"url":"https://github.com/globulus/easyflavor","last_synced_at":"2026-02-18T10:00:30.330Z","repository":{"id":54142703,"uuid":"207999146","full_name":"globulus/easyflavor","owner":"globulus","description":null,"archived":false,"fork":false,"pushed_at":"2024-08-17T15:14:43.000Z","size":922,"stargazers_count":3,"open_issues_count":1,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-24T13:34:49.953Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/globulus.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-09-12T08:15:12.000Z","updated_at":"2024-08-17T15:14:47.000Z","dependencies_parsed_at":"2022-08-13T07:30:58.103Z","dependency_job_id":null,"html_url":"https://github.com/globulus/easyflavor","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/globulus/easyflavor","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globulus%2Feasyflavor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globulus%2Feasyflavor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globulus%2Feasyflavor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globulus%2Feasyflavor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/globulus","download_url":"https://codeload.github.com/globulus/easyflavor/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/globulus%2Feasyflavor/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29575343,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T08:38:15.585Z","status":"ssl_error","status_checked_at":"2026-02-18T08:38:14.917Z","response_time":162,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2026-02-18T10:00:15.484Z","updated_at":"2026-02-18T10:00:30.324Z","avatar_url":"https://github.com/globulus.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# EasyFlavor\n\n**EasyFlavor** is a lightweight dependency injection lib for apps with several configurations/flavors that use a common codebase which slightly differs based on the flavor type.\n\nTake, for example, an app that has a trial and a full version. Their codebase is exactly the same, but the trial version has some features taken out, and some parts of the code behaving differently than the full version. With **EasyFlavor**, the differences can neatly be put in a clean architecture using a few annotations, and the boilerplate code generated automatically. Naturally, the library can scale as much as possible, handling large projects with many different flavors.\n\nThe architecture enforced by the library fits neatly into popular options, such as MVP or MVVM. It's also not meant to replace or be a dumbed-down version of other DI tools, such as [Dagger](https://github.com/google/dagger), but to [complement them](#notes) by providing boilerplate code.\n\n**EasyFlavor** uses no Android dependencies so it's shipped as a Java library, meaning that it can be used in pure Java and Android projects alike.\n\nThe library works with **Kotlin** as well as Java, as illustrated by the [demo app](app/). It also has the ability to generate additional [Kotlin extensions code](#kotlin-extensions).\n\n**AndroidX ViewModels** are fully supported via [*flavoredViewModel* property delegate](#viewmodel-support).\n\nEasyFlavor uses [MMAP](https://github.com/globulus/mmap) to allow for multi-module annotation processing. It relies purely on generated code, and not reflection, meaning that there's no performance overhead is introduced.\n\n### Installation\n\nEasyFlavor is hosted on JCenter - just add the EasyFlavor dependency and annotation processor:\n\n```gradle\ndependencies {\n   implementation 'net.globulus.easyflavor:easyflavor:1.0.6'\n   implementation 'net.globulus.easyflavor:easyflavor-annotations:1.0.6'\n   annotationProcessor 'net.globulus.easyflavor:easyflavor-processor:1.0.6'\n   // and/or\n   kapt 'net.globulus.easyflavor:easyflavor-processor:1.0.6'\n}\n```\n\n### How to use\n\n#### Define app flavors\n\nDefine your app flavors and tell *EasyFlavor* how to resolve which flavor the app is running:\n\n```java\nclass MyApp extends Application {\n    public static final String FULL = \"full\";\n    public static final String FREE = \"free\";\n    \n    @Override\n    public void onCreate() {\n        super.onCreate();\n        \n        // FlavorResolver is a simple functional interface that returns\n        // a String describing the app flavor. It's up to you to define\n        // exactly how is the app flavor determined.\n        EasyConfig.setResolver(() -\u003e (BuildConfig.FULL_VERSION) ? FULL : FREE);\n    }\n}\n```\n\n#### Designate a *Flavorable* type\n\nFor the part of code that depends on flavors, create a class or an interface and *annotate it with* **@Flavorable**. E.g, here's an interface describing a View Model of a MainActivity:\n\n```java\n@Flavorable\ninterface MainActivityViewModel {\n    void fetchData(String collection, Callback callback);\n}\n```\n\n#### Option #1 - Add a *Flavored* subclass/implementation\n\nProvide flavor-specific implementations of the interface. Annotate them with **@Flavored**, providing an array of flavors as Strings for which this implementation is valid:\n\n```java\n@Flavored(flavors = {MyApp.FREE})\nclass FreeMainActivityViewModel implements MainActivityViewModel {\n    void fetchData(String collection, Callback callback) {\n        String tag = this.getClass().getSimpleName();\n        Log.e(tag, \"FREE called for \" + collection);\n        callback.handle(tag);\n    }\n}\n```\n\n```java\n@Flavored(flavors = {MyApp.FULL})\nclass FullMainActivityViewModel implements MainActivityViewModel {\n    void fetchData(String collection, Callback callback) {\n        String tag = this.getClass().getSimpleName();\n        Log.e(tag, \"FULL called for \" + collection);\n        callback.handle(tag);\n    }\n}\n```\n\n#### Option #2 - *FlavorInject* methods directly\n\nYou may also specify methods in your *Flavorable* class that are replaced, prefixed or suffixed by other methods, depending on the app Flavor.\n\n1. Annotate those methods with **@FlavorInject**, optionally specifying the execution mode (*before*, *after*, or by default *replace*).\n\n2. Annotate flavor-specific methods with **@Flavored**, also providing an array of flavors. **The names of flavored methods must contain the name of its FlavorInject counterpart at either beginning or end**.\n\n```java\n@Flavorable\nclass FtueManager {\n    \n    @FlavorInject(mode = FlavorInject.Mode.BEFORE)\n    void fetchData(String collection, Callback callback) {\n         Log.e(this.getClass().getSimpleName(), \"Common code called\");\n    }\n    \n    @Flavored(flavors = {MyApp.FULL})\n    void fullFetchData(String collection, Callback callback) {\n         Log.e(this.getClass().getSimpleName(), \"Full version fetch data\");\n    }\n    \n    @Flavored(flavors = {MyApp.FREE})\n    void freeFetchData(String collection, Callback callback) {\n         Log.e(this.getClass().getSimpleName(), \"Free version fetch data\");\n    }\n}\n```\n\n*EasyFlavor* will generate a subclasses of your *Flavorable* that have the flavors-specific code injected into *FlavorInject* methods based on rules described above.\n\n#### Obtain flavored instances\n\nWhen instantiating the Flavorable, use **EasyFlavor.get(CLASS, ARGS...)** instead of instantiating manually:\n```java\nclass MainActivity extends Activity {\n    \n    private MainActivityViewModel viewModel;\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        ...\n        viewModel = EasyFlavor.get(MainActivityViewModel.class);\n    }\n}\n```\n\nFor classes and generated subclasses, *EasyFlavor* **automatically resolves constructors based on types and length of the passed argument list**:\n\n```java\n@Flavorable\nclass FtueManager {\n    \n    public FtueManager(String mode) {\n        ...\n    }\n    \n    public FtueManager(String mode, String tag) {\n        ...\n    }\n}\n\n...\n\nclass MainActivity extends Activity {\n    \n    private FtueManager ftueManager;\n    \n    @Override\n    protected void onCreate(Bundle savedInstanceState) {\n        ...\n        ftueManager = EasyFlavor.get(MainActivityViewModel.class, \"mode\", \"tag\");\n    }\n}\n```\n\nNote that primitive types in constructors aren't allowed given that the args list is an array of Object. For Kotlin, this isn't an issue as the processor will automatically wrap primitive types.\n\n### Multi-module support\n\nEasyFlavor supports hierarchical modules, meaning that it can [generate the final code based on multiple layers of modules](https://github.com/globulus/mmap), from top-level libraries all the way down to the app itself.\n\nBecause of this, it's **necessary to do the following**:\n\n1. Annotate one class (just one, any one) in your *top-most* module with *@EasyFlavorConfig(source = true)*.\n2. Annotate one class (again, just one) in your *bottom-most* module(s) with *@EasyFlavorConfig(sink = true)*.\n\nThat's it! These annotations will tell the processor how's your architecture oriented and allow it to generate all of its files so that no conflicts arise.\n\n### Notes\n\n* *EasyFlavor.get()* fits nicely with existing DI solutions, making it dead easy to link the EasyFlavor injection with, e.g, Dagger2:\n```kotlin\nclass MainActivity extends Activity {\n   @Inject\n   lateinit var viewModel: MainActivityViewModel\n}\n```\n\n```kotlin\n@Module\nclass ViewModelModule {\n   @Provides\n   fun provideMainActivityViewModel() = EasyFlavor.get(MainActivityViewModel::class.java)\n}\n```\n\n* If you're using EasyFlavor with multiple modules, it may be necessary to clean your project and recompile if the processor begins to complain that a certain class isn't a subtype of a *Flavorable* class.\n\n### Kotlin extensions\n\nEasyFlavor's processor can generate a Kotlin file containing functions that make it easy to run a certain block of code based on a Flavor (or multiple of them). E.g, here's what this file looks like if you have two flavors in your app, \"Free\" and \"Full\":\n\n```kotlin\npublic fun \u003cT\u003e runIf(flavors: Array\u003cString\u003e, block: () -\u003e T): T? {\n  if (flavors.contains(EasyFlavor.getResolver().resolve())) {\n    return block()\n  }\n  return null\n}\n\npublic fun \u003cT\u003e runUnless(flavors: Array\u003cString\u003e, block: () -\u003e T): T? {\n  if (!flavors.contains(EasyFlavor.getResolver().resolve())) {\n    return block()\n  }\n  return null\n}\n\npublic fun \u003cT\u003e runIfFree(block: () -\u003e T): T? {\n  if (EasyFlavor.getResolver().resolve() == \"free\") {\n    return block()\n  }\n  return null\n}\n\npublic fun \u003cT\u003e runUnlessFree(block: () -\u003e T): T? {\n  if (EasyFlavor.getResolver().resolve() != \"free\") {\n    return block()\n  }\n  return null\n}\n\npublic fun \u003cT\u003e runIfFull(block: () -\u003e T): T? {\n  if (EasyFlavor.getResolver().resolve() == \"full\") {\n    return block()\n  }\n  return null\n}\n\npublic fun \u003cT\u003e runUnlessFull(block: () -\u003e T): T? {\n  if (EasyFlavor.getResolver().resolve() != \"full\") {\n    return block()\n  }\n  return null\n}\n```\n\nThen, you can do something like this:\n\n```kotlin\nclass SomeClass {\n    fun someMethod() {\n        // Do something\n        runIfFree {\n            // Do something only if current flavor is free\n        }\n        // Do something else\n    }\n}\n```\n\nTo enable this feature, annotate one class per block with **@EasyFlavorConfig** and provide a module name to the *kotlinExtModule* param:\n\n```kotlin\n@EasyFlavorConfig(kotlinExtModule = \"MyModule\")\nclass SomeClassInModule\n```\n\nSpecifying unique names for your modules allows you to use these functions from all your modules, regardless of their hierarchy. Omitting the module will make it so that Kotlin files aren't generated (if, e.g, you only use Java in your project).\n\n### ViewModel Support\n\nIf your *@Flavorable* class extends [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel), you can inject it via the **flavoredViewModel** property delegate:\n\n```kotlin\n@Flavorable\nopen class MainActivityViewModel : ViewModel() {\n\n  @FlavorInject\n  open fun getTitle() = \"\"\n\n  @Flavored(flavors = [AppFlavors.FREE])\n  internal fun getTitleFree() = \"FREE\"\n\n  @Flavored(flavors = [AppFlavors.FULL])\n  internal fun getTitleFull() = \"FULL\"\n}\n```\n\n```kotlin\n\nclass MainActivity : AppCompatActivity() {\n\n  private val viewModel: MainActivityViewModel by flavoredViewModel()\n```\n\n*flavoredViewModel* will internally use the *ViewModelProvider* to instantiate the appropriate flavored instance for your ViewModel. Note that you can pass any arguments to *flavoredViewModel* as you would for *EasyFlavor.get*, except the class, as it's inferred automatically.\n\nThis requires the *easyflavor-android* dependency to be added:\n\n```gradle\ndependencies {\n   implementation 'net.globulus.easyflavor:easyflavor-android:1.0.6.1'\n}\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglobulus%2Feasyflavor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglobulus%2Feasyflavor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglobulus%2Feasyflavor/lists"}