Giter VIP home page Giter VIP logo

vault's Introduction

Contentful Java SDK
Join Contentful Community Slack ย  Join Contentful Community Forum

vault - Contentful Offline Persistence for Android

Build Status

Vault is an Android library that simplifies persistence of Resources from Contentful via SQLite. It defines a Java representation of Contentful models. At compile-time Vault creates a corresponding database schema by generating all the required boilerplate code and injecting it into the classpath. It is also bundled with a complementary lightweight runtime which exposes a simple ORM-like API for pulling resources from the generated database.

What is Contentful?

Contentful provides a content infrastructure for digital teams to power content in websites, apps, and devices. Contentful, unlike any other CMS, is built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship digital products faster.

Table of contents

Setup

Install the dependency by

  • Maven
<dependency>
  <groupId>com.contentful.vault</groupId>
  <artifactId>compiler</artifactId>
  <version>3.2.6</version>
</dependency>
<dependency>
  <groupId>com.contentful.vault</groupId>
  <artifactId>core</artifactId>
  <version>3.2.6</version>
</dependency>
  • Gradle
apt 'com.contentful.vault:compiler:3.2.6'
compile 'com.contentful.vault:core:3.2.6'
  • Gradle 3.+
annotationProcessor 'com.contentful.vault:compiler:3.2.6'
annotationProcessor 'com.contentful.vault:core:3.2.6'
compile 'com.contentful.vault:core:3.2.6'

Note for Gradle: Use the android-apt Gradle plugin, which configures compile-time dependencies only. Note for Gradle 3.0 and newer: Use the annotationProcessor instead of apt.

Snapshots

Snapshots of the development version are available in Sonatype's snapshots repository.

Usage

Models and Fields

Models are defined by declaring a subclass of the Resource class. Annotate the class with @ContentType, which takes the Content Type's ID as its value.

Fields are defined by annotating class attributes with the @Field annotation:

@ContentType("cat")
public class Cat extends Resource {
  @Field public String name;
  @Field public Cat bestFriend;
  @Field public Asset image;
}

By default, the name of the attribute is used as the field's ID, but can also be specified explicitly:

@Field("field-id-goes-here") 
public String someField; 

Field ids are escaped, however when making queries with a WHERE condition it is up to the caller to escape the field name in case it is a reserved keyword. For example:

@ContentType("...")
public class Foo extends Resource {
  @Field public String order;
}

Since order is a reserved SQLite keyword, making a query which references that field is done as following:

vault.fetch(Foo.class)
    .where("`" + Foo$Fields.ORDER "` = ?", "bar")
    .first();

Spaces

Spaces are classes annotated with the @Space annotation. It is required to specify the Space ID, an array of Model classes and an array of locale codes wanted to be persisted:

@Space(
    value = "cfexampleapi",
    models = { Cat.class },
    locales = { "en-US", "tlh" }
)
public class DemoSpace { }

Synchronization

Once a Space is defined, Vault is invoked to synchronize the local database with Contentful:

// Client
CDAClient client = CDAClient.builder()
    .setSpace("cfexampleapi")
    .setToken("b4c0n73n7fu1")
    .build();

// Sync
Vault.with(context, DemoSpace.class).requestSync(client);

Vault uses a worker thread to request updates from the Sync API and reflect the changes in its database. Once sync is completed, Vault fires a broadcast with the action Vault.ACTION_SYNC_COMPLETE.

Providing a SyncCallback results in it beeing invoked once sync is completed:

class SomeActivity extends Activity {
  SyncCallback callback;
  
  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    Vault.with(this, DemoSpace.class).requestSync(client, callback = new SyncCallback() {
      @Override public void onResult(SyncResult result) {
        if (result.isSuccessful()) {
          // Success \o/
        } else {
          // Failure
        }
      }
    });
  }
  
  @Override protected void onDestroy() {
    Vault.cancel(callback);
    super.onDestroy();
  }
}

Note: Extra care needs to be taken for the lifecycle: Cancelling the callback on lifecycle events is important.

Similarly using RxJava notifies of sync results via an Observable:

Vault.observeSyncResults() // returns Observable<SyncResult>

Queries

Vault provides a wrapper around its generated database which fetches persisted objects:

Vault vault = Vault.with(context, DemoSpace.class);

// Fetch the first Cat
vault.fetch(Cat.class).first();
    
// Fetch the most recently updated Cat
vault.fetch(Cat.class)
    .order(Cat$Fields.UPDATED_AT + " DESC")
    .first();

// Fetch a Cat with a specific name
vault.fetch(Cat.class)
    .where(Cat$Fields.NAME + " = ?", "Nyan Cat")
    .first();

// Fetch a Cat with a specific name pattern
vault.fetch(Cat.class)
    .where(Cat$Fields.NAME + " LIKE ?", "%Nyan%")
    .first();

// Fetch a Cat with a specific boolean field
// SQLite is storing booleans as 0/1 
vault.fetch(Cat.class)
    .where(Cat$Fields.IS_GRUMPY + " = ?", "1")
    .first();
    
// Fetch all Cats, ordered by creation date:
vault.fetch(Cat.class)
    .order(Cat$Fields.CREATED_AT)
    .all();

// Fetch all Cats, using the Klingon locale:
vault.fetch(Cat.class)
    .all("tlh");

Using RxJava queries are created by the observe() method, for example:

vault.observe(Cat.class)
    .where(Cat$Fields.NAME + " = ?", "Happy Cat")
    .all() // returns Observable<Cat>

The above example creates an Observable that subscribes and observes on the same thread initiating the query. However, it changes if this typical use-case is used:

vault.observe(Cat.class)
    .all()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())

Migrations

Whenever changes are introduced to any of the previously used Models, a migration has to be applied. Increment the version number to trigger a migration:

@Space(value = "cfexampleapi", models = { Cat.class }, dbVersion = 2)
public class DemoSpace { }

Note: this deletes any previously persisted data and reaquires them.

Preseeding

Depending on the amount of content in a given space, initial synchronization might take some time. For that support to pre-seed the database with static content got added.

For creating an initial database file, use the VaultDatabaseExporter. This class takes an Android Context and a Vault Space. Calling the .export(..) method, it creates a sqlite database in src/main/assets/initial_seed.db. Vault is instructed to use this as follows:

@Space(
    value = "{spaceid}", // space id of the space to use
    models = { Cat.class },  // model classes to be used
    copyPath = "initial_seed.db" // name of the just created database file.
)
public class VaultSpace { }

The database file is updated by leveraging a robolectric test before releasing. This test syncs Contentful data to the existing database.

A simple test suite looks like this:

@RunWith(RobolectricTestRunner.class)
public class TestSeedDB {
 @literal @Test
  public void testSyncDBtoSqlite() throws Exception {
    final Activity activity = Robolectric.setupActivity(Activity.class);

    assertTrue(new VaultDatabaseExporter().export(activity, VaultSpace.class, VaultSpace.TOKEN));
  }
}

The database content will always be uptodate when those tests get executed. If an error happens this test will fail and information about next steps will be given.

Note: In order to add this functionality to an already shipped app, the dbVersion value has to be increased, as it causes invalidation of any pre-existing content.

ProGuard

Grab the ProGuard configuration file and apply to your project.

Documentation

Javadoc is available here.

License

Copyright 2017 Contentful, GmbH.

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.

Reaching Contentful

Questions

  • Use the community forum: Contentful Community Forum
  • Use the community slack channel: Contentful Community Slack

Bugs and Feature Requests

  • File an issue here File an issue.

Sharing Confidential Information

  • File a support ticket at Contentful Customer Support: File support ticket

Getting involved

PRs Welcome

Code of Conduct

Contentful wants to provide a safe, inclusive, welcoming, and harassment-free space and experience for all participants, regardless of gender identity and expression, sexual orientation, disability, physical appearance, socioeconomic status, body size, ethnicity, nationality, level of experience, age, religion (or lack thereof), or other identity markers.

Full Code of Conduct.

vault's People

Contributors

kzotin avatar mariobodemann avatar miron4dev avatar neonichu avatar phoebeschmidt avatar rafalniski avatar tomxor 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

Watchers

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

vault's Issues

Documentation on how to represent Links to other Entry types in models

Would you be able to describe how we would represent Links to other resources in the model itself?
At the moment, we have a content type that references other entry types but do not know how to represent it in the data models.

Edit - to clarify, these links in the data model can refer to many different content types

How to use LIKE operator for fetching items

Hi,
I'm desperately trying to fetch all items whose Title contains MY_QUERY with vault + RxJava.

I've tried tons of variants, including:
.observe(Guide.class).where("title LIKE '%?%'", "MY_QUERY").all()
.observe(Guide.class).where("query", "MY_QUERY").all()

And many other options... Can you advise, please?

SQLiteException on tables upgrade

I've got this exception while upgrading tables (new value in dbVersion). It seems the table name with "-" is not allowed.

Caused by: android.database.sqlite.SQLiteException: near "-": syntax error (code 1): , while compiling: DROP TABLE assets$en-US at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method) at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:887) at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:498) at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588) at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58) at android.database.sqlite.SQLiteStatement.<init>(SQLiteStatement.java:31) at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1674) at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1605) at com.contentful.vault.SqliteHelper.deleteTables(SqliteHelper.java:128) at com.contentful.vault.SqliteHelper.onUpgrade(SqliteHelper.java:70)

Throw nicer error when initialized with invalid class

Example:

static class Foo { }
Vault vault = Vault.with(context, Foo.class); // passes
vault.requestSync(...); // fails

Exception:

java.lang.RuntimeException: java.lang.ClassNotFoundException: 
com.pkg.Whatever$Foo$$SpaceHelper
    at com.contentful.vault.Vault.createSpaceHelper(Vault.java:157)
    at com.contentful.vault.Vault.getOrCreateSqliteHelper(Vault.java:173)
    at com.contentful.vault.Vault.requestSync(Vault.java:107)
    at com.contentful.vaultintegration.BaseTest.sync(BaseTest.java:122)
    at com.contentful.vaultintegration.BaseTest.sync(BaseTest.java:98)
    at com.contentful.vaultintegration.ArrayTest.testArray(ArrayTest.java:40)

This should fail faster, right when Vault.with() is invoked. The error message should also be more informative.

Download all available locales

Hi.

Is there a way to download content for all available locales without knowing those specifically?

Because now, when I add new locale to contentful, I should add it to annotation and release new build to Play Market.

Entry title

Contentful defines title for each content type. Would be possible to access the content of this field using a single method, something like:
resource.title()

Asset metadata is incomplete

Currently only the URL and mime-type of an asset are provided by the Asset class. title should also be supported as well as the file JSON element and description.

Vault enters an unmanagable loop when loading sub objects

Hi.

This is more of a problem with our content model itself but it would be a good idea to raise this as an issue and safeguard against it because it causes the app to freeze

At the moment, I have Content Type A that references Content Type B.
Due to poor design on our part, Content Type B contains links to Content Type A which causes the looping problem.

I do understand that the quickest way to solve this is to fix the links between the content types, but is there a strategy to introduce into the vault ORM that prevents such links from being loaded?

Inject class with model's db column names

@ContentType("foo")
public class Foo extends Resource {
  @Field String bar;
  @Field String baz;
}

should generate:

public class Foo$Fields {
  public static final String BAR "bar-db-column-name";
  public static final String BAZ "baz-db-column-name";
}

ORM is not loading the correct localised Asset

Hi.

I have come across a bug where if I have a localised Asset field, it will not load the correct Asset file, it will load the asset for the default locale.

The localised text fields are being loaded correctly, just the wrong asset is being loaded.

What is vault_file_name.db ? Where to get it from?

In seeding section I saw it being mentioned to put file "vault_file_name.db" in assets folder and then refer it while declaring space. But no where I found it mentioned what "vault_file_name.db" is and how to generate or download it ?

Content types with non localised symbols are NULL when sync'ed

Hi.

I was trying to create a new content type to sync and I am currently having some trouble syncing the non localised fields.

I inspected the database output and the identifier and helpKey fields are currently set to NULL for all the entry_{id}${locale} tables

Only the content field is downloaded and it is because it is localisable.

I have tested it against vault 0.9.14 and 2.0.0 and it has the same problem

Here is the model definition

@ContentType("helpContent")
public class Helpcontent extends Resource {
	@Field String identifier;
	@Field String content;
	@Field String helpKey;

	public Helpcontent() {
	}

	public String identifier() {
		return identifier;
	}

	public String helpKey() {
		return helpKey;
	}

	public String content() {
		return content;
	}
}

And here is the JSON preview

{
  "name": "Help content",
  "description": "",
  "displayField": "identifier",
  "fields": [
    {
      "id": "identifier",
      "name": "Identifier",
      "type": "Symbol",
      "localized": false,
      "required": true,
      "validations": [
        {
          "unique": true
        }
      ],
      "disabled": false,
      "omitted": false
    },
    {
      "id": "content",
      "name": "Content",
      "type": "Text",
      "localized": true,
      "required": true,
      "validations": [],
      "disabled": false,
      "omitted": false
    },
    {
      "name": "helpKey",
      "id": "helpKey",
      "type": "Symbol"
    }
  ],
  "sys": {
    "id": "helpContent",
    "type": "ContentType",
    "createdAt": "2016-11-02T14:28:38.538Z",
    "createdBy": {
      "sys": {
        "type": "Link",
        "linkType": "User",
        "id": "5u4wmZIO25TuaI2AWA4pJq"
      }
    },
    "space": {
      "sys": {
        "type": "Link",
        "linkType": "Space",
        "id": "vo0tsnu0x6xh"
      }
    },
    "firstPublishedAt": "2016-11-02T14:28:53.932Z",
    "publishedCounter": 2,
    "publishedAt": "2016-11-03T15:27:58.070Z",
    "publishedBy": {
      "sys": {
        "type": "Link",
        "linkType": "User",
        "id": "5u4wmZIO25TuaI2AWA4pJq"
      }
    },
    "publishedVersion": 3,
    "version": 4,
    "updatedAt": "2016-11-03T15:27:58.077Z",
    "updatedBy": {
      "sys": {
        "type": "Link",
        "linkType": "User",
        "id": "5u4wmZIO25TuaI2AWA4pJq"
      }
    }
  }
}

Fields null when sync

Hi,

I'm facing a problem, when i try to synchronize the contentful database, the most part of fields get null value and i don't know why, i'm using the default locale 'tlh'. I show you an example of one object result:

Brand{companyName='null', logo=null, companyDescription='null', website='null', twitter='null', email='null', phone=[+45 35 55 11 22]}

I only get values on list fields like 'phone' in this case.

Greetings

Exception thrown after the content type is changed

I added a new field into existing content type. Model object was updated to reflect this field. Also I had previously many entries created for this content type. I got then the following exception in ModelHelper of that content type (fromCursor method):

Caused by: java.lang.IllegalStateException: Couldn't read row 0, col 6 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it. at android.database.CursorWindow.nativeGetString(Native Method) at android.database.CursorWindow.getString(CursorWindow.java:438) at android.database.AbstractWindowedCursor.getString(AbstractWindowedCursor.java:51)

I think a null check should be added somewhere inside the appendFromCursor method of com.contentful.vault.compiler.ModelInjection class.

NoClassDefFoundError: com/contentful/vault/ContentType after upgrading to Android Studio 3.0 Canary 3

Hi,
After upgrading to Android Studio 3.0 Canary 3 and migrating everything according to the guide, project fails to build and shows
NoClassDefFoundError: com/contentful/vault/ContentType

My build.gradle contains:

    buildToolsVersion "26.0.0-rc2"
...
    implementation 'com.contentful.vault:core:2.1.0'
    annotationProcessor 'com.contentful.vault:compiler:2.1.0'
    
    implementation 'com.squareup.okhttp:okhttp:2.3.0'
    implementation 'com.squareup.okhttp:okhttp-urlconnection:2.3.0'
... 
    // ButterKnife compiles well
    implementation 'com.jakewharton:butterknife:8.6.0'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.6.0'

Thanks in advance.

I'm getting a NoClassDefFoundError for the retrofit.RestAdapter in the CDA client only on android api 19(4.4.4) on a Huawei Y635-l01.

This issue in not happening in any other device

These are the related dependencies. As you can see i'm using retrofit in the app as well:
apt 'com.contentful.vault:compiler:2.0.0'
compile'com.contentful.vault:core:2.0.0'
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.0.0'
compile('com.squareup.retrofit2:adapter-rxjava:2.0.0') {
exclude module: 'rxjava'
exclude module: 'rxandroid'
}

I'm building the CDAClient in the application class' onCreate().

Fatal Exception: java.lang.NoClassDefFoundError: retrofit.RestAdapter$Builder
at com.contentful.java.cda.CDAClient.createService(CDAClient.java:54)
at com.contentful.java.cda.CDAClient.(CDAClient.java:38)
at com.contentful.java.cda.CDAClient.(CDAClient.java:23)
at com.contentful.java.cda.CDAClient$Builder.build(CDAClient.java:265)
at uk.co.activityhunter.activityhunter.util.ProjectUtils.setCDAClient(ProjectUtils.java:105)
at uk.co.activityhunter.activityhunter.application.AHApp.onCreate(AHApp.java:26)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1009)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4632)
at android.app.ActivityThread.access$1800(ActivityThread.java:141)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1298)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5336)
at java.lang.reflect.Method.invokeNative(Method.java)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:873)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:689)
at dalvik.system.NativeStart.main(NativeStart.java)

Support multiple locales on the one app

Is there a way to have multiple languages persisted on the device? This is quite a big feature of our app and if we cannot have multiple languages persisted we will need to roll our own implementation.

At the moment, there is only one language supported with a single .sqllite file. If it was possible, we would prefer to have the id of the database file linked to the locale as well. That way we can switch the locale and language easily.

There is also evidence that switching to another language will not overwrite the previous language. Do we have to remove the space and start over?

Hint about contenttype

Hi there,

I was struggling for some days why my stuff wasn't synced. The Problem was I wrote the contenttypes with an capital letter in the beginning, because it's displayed in the web interface of contentful. I think it would be a good idea to examine users of vault to have a look into the json preview and search for the contenttype.

Best
Alex

Using the Preview API

Hi,

I was wondering if it possible to use the Preview API + key with this Vault library?

How do we use the Vault library using the preview api token?

Is it supported?

I am getting an exception even though there is a function in the CDAClient to use the preview API.

java.lang.UnsupportedOperationException: Syncing using a preview token is not supported. Please use a production token.

Concurrent Modification Exception thrown

Hi.

I am getting a very strange error all of a sudden where the requestSync action fails to complete and we are getting a java.util.ConcurrentModificationException

It looks like it is caused by Retrofit.

OnErrorThrowable$OnNextValue "rx.exceptions.OnErrorThrowable$OnNextValue: OnError while emitting onNext value: retrofit.client.Response.class"

OnError while emitting onNext value: retrofit.client.Response.class"

Is there a way to trace the source of this problem?

It was working fine last week. The only thing that has changed is the volume of localised content. Which is quite large.

Another model type

How feasible would be to use a different model type in Vault? Instead of using:

@ContentType("cat")
public class Cat extends Resource {
  @Field public String name;
  @Field public Cat bestFriend;
  @Field public Asset image;
}

define model in a different way:

public class Entry extends Resource {
  Map<String, Object> fields;
}

What I'm going to achieve is to fetch different content type objects in a single line of code, e.g.:

vault.fetch(Entry.class).where("contentType = ?", "Cat")

RxJava support

Since links are represented on different tables this currently limits the query abilities when applying a where() clause, as one cannot add conditions based on linked entries:

vault.fetch(Cat.class)
    .where("bestFriend.remote_id = ?", "foo") // won't work
    .all();

This forces the caller to filter the results manually. Among many other benefits, supporting queries with RxJava could make this a little bit less tedious, for example:

vault.observe(Cat.class)
    .filter((cat) -> "FOO".equals(cat.bestFriend().remoteId())
    .subscribe((cat) -> ...);

Model with no fields

@ContentType("foo")
public class Foo extends Resource { }

Currently results in a compile-time error:

Error:(X, X) error: Failed writing injection for "com.package.Foo", message: cannot unindent 1 from 0

This exception is raised due to incorrect use of JavaPoet since ModelInjection.java assumes at least one field was defined. Defining a model with no fields is an error, and a more friendly error message should indicate that.

Replace query pipeline with a type-safe one

Making queries right now forces the caller to hardcode strings or concatenate them:

vault.fetch(Foo.class).where("gross = ?", bar)
vault.fetch(Foo.class).where(Foo$Fields.GROSS + " = ?", bar);

An alternative approach should be considered, one that potentially could also infer that JOIN clauses are required to fulfill the query.

NPE when an object has link references caused by predefined string values in model

I was able to load up my content object using vault until I started added some links to other content types. It started to crash with the following stacktrace.

java.lang.NullPointerException
            at com.contentful.vault.LinkResolver.getCachedResourceOrFetch(LinkResolver.java:103)
            at com.contentful.vault.LinkResolver.resolveLinksForField(LinkResolver.java:70)
            at com.contentful.vault.LinkResolver.resolveLinks(LinkResolver.java:58)
            at com.contentful.vault.QueryResolver.resolveLinks(QueryResolver.java:81)
            at com.contentful.vault.QueryResolver.all(QueryResolver.java:65)
            at com.contentful.vault.FetchQuery.all(FetchQuery.java:27)
            at com.contentful.vault.FetchQuery.first(FetchQuery.java:31)
            at com.contentful.vault.FetchQuery.first(FetchQuery.java:43)

What else do you need to know to debug this scenario?

Querying for content based on linked sub objects

Hi.

We are starting to create some complex queries and was trying to figure out how we can search for content based on linked objects.

What sort of ORM model are you using and does it allow table joins?
Is it possible to query for content using a SQL statement?

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.