GithubHelp home page GithubHelp logo

uber / artist Goto Github PK

View Code? Open in Web Editor NEW
210.0 22.0 20.0 533 KB

An artist creates views. Artist is a Gradle plugin that codegens a base set of Android Views.

License: Apache License 2.0

Kotlin 99.40% Shell 0.60%
android view codegen plugin kotlin

artist's Introduction

Artist Build Status

As Android apps grow, providing common features and consistent functionality across Views becomes challenging. Typically, this results in copy-pasting features across views, monolithic classes, or complicated inheritance trees. Artist is a highly-extensible platform for creating and maintaining an app’s base set of Android views.

Overview

Artist is a Gradle plugin written in Kotlin that generates a base set of Android Views. Artist-generated views are created using a stencil and trait system. Each view type is declared with a single stencil, which is comprised of a set of traits. All of this comes together to create an easily maintainable system of stencils and traits.

Stencils: A Stencil defines a View class to be generated. Each Stencil has some properties that can be configured and declares a set of traits they exhibit.

Traits: A Trait defines the new functionality that should be added to a view. It is a hook into the Stencil’s codegen process that is called during each Stencil’s generation. It is responsible for generating the code that implements Trait's functionality. This could be used to do things like add automatic view analytics to every view or add first-party support for RxBinding APIs (clicks, attach events, visibility changes, etc.) on all your views.

A simple Trait that adds visibility helper methods would look like:

@AutoService(JavaTrait::class)
class VisibilityTrait : JavaTrait {
  override fun generateFor(type: Builder, initMethod: MethodSpec.Builder, rClass: ClassName, baseType: String) {
    arrayOf("visible", "invisible", "gone")
        .forEach { type.addMethod(createVisibilityConvenienceMethod(it)) }
  }

  private fun createVisibilityConvenienceMethod(type: String): MethodSpec {
    return MethodSpec.methodBuilder("is${type.capitalize()}")
        .addModifiers(Modifier.PUBLIC)
        .returns(TypeName.BOOLEAN)
        .addStatement("return getVisibility() == \$T.${type.toUpperCase()}", TypeNames.Android.View)
        .build()
  }
}

A simple ViewStencil to generate a Switch with visibility helper methods would look like:

class SwitchStencil : JavaViewStencil(
    extendedType = "android.support.v7.widget.SwitchCompat",
    constructorCount = 3,
    defaultAttrRes = "switchStyle",
    addedTraits = VisibilityTrait::class.java) {
  
  override fun name() = "MySwitch"
}

Finally leaving you with a generated view like this:

public class MySwitch extends SwitchCompat {
  // Constructors
  
  // protected init method - provided in every stencil
  
  public boolean isVisible() {
    return getVisibility() == View.VISIBLE;
  }
  
  public boolean isGone() {
    return getVisibility() == View.GONE;
  }
  
  public boolean isInvisible() {
    return getVisibility() == View.INVISIBLE;
  }
}

This may look like a lot of boilerplate for simple helpers, but it scales quite well when you want to have these methods on all your base views.

Motivation

Common Façade

Everything is behind the façade of commonly named classes, basically "[YOUR_PREFIX]ViewName". This allows you to push as much functionality as you want behind them whilst not changing the front facing entry point. Things we can push behind them include new functionality, other base classes, framework bug fixes, etc.

Sane, simple maintainability

The stencil and trait system ensures that base views are defined in one place and that extra functionality is divided up into single-focus traits.

Reactive Semantics

Artist-generated views can have RxBinding APIs as first class citizens in their public APIs. In a increasingly reactive world, this gracefully bridges common UI listener interactions to RxJava streams. This can optionally be brought in via the artist-traits-rx module.

Intelligence

Artist-generated views have deep internal knowledge of their internal state and interactions. This gives you flexibility to do a number of interesting, contextual actions under the hood.

Automatic Instrumentation: Artist-generated views know when they're being attached, changed to visible, clicked, etc. This allows you to do automatic instrumentation of impressions and taps in views when they occur, provided the developer has provided an ID. You can also detect and signal a developer if an ID is missing where there should be one.

Accessibility: This intelligence gives you enough insight into the state of the view hierarchy to make accessibility a first class citizen in the daily development cycle. Artist-generated views can intelligently infer if there are content description errors associated with them, and signal them to developers in the apps.

For more examples of things you can do with Artist, check out the Recipes wiki page.

Usage

Create the Provider module

  • Create a new plain Java/Kotlin module (non-Android)
  • Add Artist dependencies (API, Traits, Traits-Rx)

Implement the Stencil Provider

  • Create a class that implements JavaViewStencilProvider
  • Annotate your class with @AutoService(JavaViewStencilProvider::class)

Implement Custom Traits (Optional)

  • If you have custom traits, then create classes that implement JavaTrait
  • Annotate those classes with @AutoService(JavaTrait::class)

Add Provider module to Plugin Classpath

Option #1

If your provider module is in it's own project, then you can add the JAR to the buildscript classpath in your main project's root build.gradle like:

buildscript {
  dependencies {
    classpath <include for your jar>
  }
}

Option #2

Otherwise, if your provider module is in your primary project, then in order for Artist to find the classes on the plugin classpath during code generation, we must leverage Gradle's buildSrc. We use this project within your project to build the classes that will be added to the plugin classpath. This will run before your primary project is built.

  • Create a dir at root of project named buildSrc
  • Navigate to buildSrc and add a relative symlink to the provider module cd $PROJECT_ROOT/buildSrc; ln -s ../path/to/provider/module/root custom-artist-providers
  • Create a settings.gradle in buildSrc and add include :custom-artist-providers
  • Update the build.gradle for the buildSrc project to ensure that the custom-artist-providers module is added the buildScript classpath so it is available to the Artist plugin:
subprojects { subproject ->
    if (subproject.buildFile.exists()) {
        repositories {
            jcenter()
            google()
        }

        rootProject.dependencies {
            runtime project(path)
        }
    }
    subproject.afterEvaluate {
        // Disable useless tasks in buildSrc
        if (subproject.plugins.hasPlugin("kotlin")) {
            subproject.tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
                kotlinOptions.suppressWarnings = true
            }
        }

        subproject.tasks.findAll {
            it.name.toLowerCase().contains("test") ||
                it.name.toLowerCase().contains("lint") ||
                it.name.toLowerCase().contains("checkstyle") }.each {
            it.enabled = false
        }
    }
}

Use the Generated Views

The generated views will be added to the library's source files. They can then be consumed as regular views. To add even more consistency, you can write a lint rule or ErrorProne check to ensure that all View subclasses use your Artist-generated views.

Further examples

The set of JavaViewStencils that Artist should process are provided via the JavaViewStencilProvider. The sample's ViewStencilProvider would configure Artist to generate these Views.

Download

Artist Plugin Maven Central

classpath 'com.uber.artist:artist:0.4.9'

Artist API Maven Central

classpath 'com.uber.artist:artist-api:0.4.9'

Artist Traits Maven Central

classpath 'com.uber.artist:artist-traits:0.4.9'

Artist Rx Traits Maven Central

classpath 'com.uber.artist:artist-traits-rx:0.4.9'

License

Copyright (C) 2017 Uber Technologies

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

artist's People

Contributors

edbertchan avatar hamza1592 avatar jbarr21 avatar kageiit avatar krishmasand avatar naturalwarren avatar oliviernotteghem avatar sanchezz93 avatar vanniktech avatar zacsweers 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

artist's Issues

Fix Travis CI build failure due to Fossa

Travis CI logs:

TROUBLESHOOTING: A FOSSA API key is needed to run this command.
Running `fossa analyze` performs a dependency analysis and uploads the result
to FOSSA. To run an analysis without uploading results, run:
    fossa analyze --output
You can provide your API key by setting the $FOSSA_API_KEY environment
variable. For example, try running:
    FOSSA_API_KEY=<YOUR_API_KEY_HERE> $command
You can create an API key for your FOSSA account at:
    
    app.fossa.com/account/settings/integrations/api_tokens

Add support for generating Kotlin views

A layer of indirection will be added to Artist such that the core logic remains the same, but JavaPoet/KotlinPoet usages will be generified. The built-in traits will be switched to KotlinTraits.

Readme doesn't give a concrete example use case

When reading Artist's readme it is hard to answer the question "what kind of problems will this solve for me?" Maybe mention a concrete example use case? For example: "this can be used for adding automatic analytics to every view".

Close this issue if you disagree.

Recipes page for potential use cases

Add a Wiki page with a list of use cases such as:

  • Adding support for automated tap & impression analytics
  • Backport bugfixes for unsupported functionality
  • Make theming fixes that everyone will inherit

Rx Kotlin Nulls

  • When generating code in Artist from the KotlinApiHelper we get the following
public open override fun clicks(): Observable<Unit> {
    if (clicks == null) {
    	....
         rxview_longCl().map()
    		.doOnNext(UiChecks.checkClickableRequirementsAction(this))
    		.doOnNext(ViewAnalytics.logTapFor(this, getContext())).subscribe(clicks)
    	}
    }
   ....
  }

The Kotlin compiler assumes that the clicks in the subscribe method is null even though we set it a few lines prior. A possible fix for this is adding a let call before the rx chain. This way we will prevent the compiler from prompting

smart cast to 'PublishRelay<Unit>' is impossible, because 'clicks' is a mutable property that could have been changed by this time

How we're planning on solving it

public open override fun clicks(): Observable<Unit> {
    if (clicks == null) {
    	....
        clicks?.let {
         rxview_longCl().map()
    		.doOnNext(UiChecks.checkClickableRequirementsAction(this))
    		.doOnNext(ViewAnalytics.logTapFor(this, getContext())).subscribe(it)
    	 }
       }
    }
    ....
  }

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.