Giter Site home page Giter Site logo

theapache64 / retrosheet Goto Github PK

View Code? Open in Web Editor NEW
831.0 13.0 38.0 17.93 MB

πŸ“ƒ Turn Google Spreadsheet to JSON endpoint (for Android and JVM) for FREE (100%)

License: Apache License 2.0

Kotlin 100.00%
retrofit okhttp interceptors google-sheets rest-api

retrosheet's Introduction

retrosheet πŸ“„

Turn Google Spreadsheet to JSON endpoint. [For Android and JVM].

https://github.com/theapache64/notes

Benefits πŸ€—

  • πŸš€ Use Google's server for reliable performance.
  • ⚑ Benefit from fast responses and no bandwidth limits.
  • πŸ”„ Migrate to your REST API with minimal code changes.
  • πŸ“Š Manage data directly through the Google Spreadsheet app.
  • πŸƒβ€β™‚οΈ Speed up development of your POC or MVP with this library.

Install 🀝

latestVersion

repositories {
  maven { url 'https://jitpack.io' } // Add jitpack
}

dependencies {
  implementation 'com.github.theapache64:retrosheet:<latest.version>'
}

Usage ⌨️

Writing Data ✍️

Step 1: Create a Google Form πŸ“

Create a form with required fields. Google Form

Step 2: Set Response Destination 🎯

Choose a Google Sheet to save responses. Response Destination Sheet Selection

Step 3: Customize Sheet πŸ“Š

Rename sheet and columns (optional). Before After

Step 4: Get Form Link πŸ”—

Press Send and copy the link. Form Link

Step 5: Create RetrosheetInterceptor πŸ”§

val retrosheetInterceptor = RetrosheetInterceptor.Builder()
    .setLogging(false)
    .addSheet("notes", "created_at", "title", "description")
    .addForm(ADD_NOTE_ENDPOINT, "Form Link")
    .build()

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(retrosheetInterceptor) // and attach the interceptor
    .build()

Step 6: Create API Interface 🌐

interface NotesApi {
    @Read("SELECT *") 
    @GET("notes")
    suspend fun getNotes(): List<Note>

    @Write
    @POST(ADD_NOTE_ENDPOINT)
    suspend fun addNote(@Body addNoteRequest: AddNoteRequest): AddNoteRequest
}

@Write is used for writing data and @Read: for reading data

Query Language Guide

Reading Data πŸ“–

Step 7: Share Sheet πŸ”„

Open a sheet and copy its shareable link. Copy Link

Step 8: Edit Link βœ‚οΈ

Trim the link after the last '/'.

https://docs.google.com/spreadsheets/d/1IcZTH6-g7cZeht_xr82SHJOuJXD_p55QueMrZcnsAvQ/edit?usp=sharing

Step 9: Set Base URL πŸ”—

Use the trimmed link as baseUrl in Retrofit or OkHttp. Set Base URL

Done πŸ‘

Full Example 🌟

import com.squareup.moshi.Moshi
import com.github.theapache64.retrosheet.RetrosheetInterceptor
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory

/**
 * Created by theapache64 : Jul 21 Tue,2020 @ 02:11
 */
fun main() = runBlocking {
  
    // Building Retrosheet Interceptor
    val retrosheetInterceptor = RetrosheetInterceptor.Builder()
        .setLogging(false)
        // To Read
        .addSheet(
            "notes", // sheet name
            "created_at", "title", "description" // columns in same order
        )
        // To write
        .addForm(
            "add_note",
            "https://docs.google.com/forms/d/e/1FAIpQLSdmavg6P4eZTmIu-0M7xF_z-qDCHdpGebX8MGL43HSGAXcd3w/viewform?usp=sf_link" // form link
        )
        .build()

    // Building OkHttpClient 
    val okHttpClient = OkHttpClient.Builder()
        .addInterceptor(retrosheetInterceptor) // and attaching interceptor
        .build()


    val moshi = Moshi.Builder().build()

    // Building retrofit client
    val retrofit = Retrofit.Builder()
        // with baseUrl as sheet's public URL    
        .baseUrl("https://docs.google.com/spreadsheets/d/1YTWKe7_mzuwl7AO1Es1aCtj5S9buh3vKauKCMjx1j_M/") // Sheet's public URL
        // and attach previously created OkHttpClient
        .client(okHttpClient)
        .addConverterFactory(MoshiConverterFactory.create(moshi))
        .build()

    // Now create the API interface
    val notesApi = retrofit.create(NotesApi::class.java)
  
    // Reading notes
    println(notesApi.getNotes())

    // Adding note
    val addNote = notesApi.addNote(
        AddNoteRequest("Dynamic Note 1", "Dynamic Desc 1")
    )
    println(addNote)
    Unit
}

Samples 🌠

Contributing

This project is applying ktlint (without import ordering since it's conflicted with IDE's format). Before creating a PR, please make sure your code is aligned with ktlint (./gradlew ktlint). We can run auto-format with:

./gradlew ktlintFormat

Retrosheet JS

  • Coming Soon

Author ✍️

  • theapache64

retrosheet's People

Contributors

imgbotapp avatar theapache64 avatar tuanchauict 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

retrosheet's Issues

Cyryllic symbols don't writing

val notesApi = retrofit.create(NotesApi::class.java)
val addNote = notesApi.addNote(
AddNoteRequest("Hello", "зСмля")
)

In sheets I see only Hello and empty

Support for accent marks

Letters with accent marks (Γ‘, Γ£, Γ©, Γ­, Γ³, Γ§, etc) are not making it to the Form, and, consequently, to the Sheet.
For example, "crΓ©dito" becomes "crdito".
Is it possible for retrosheet to support this, or is it a limitation of the Google Forms API?

Improve API

Improve API structure with more intuitive method names and method cchaining

Enable to add adapter to Moshi

The library uses an global Moshi instance that can't me modified.

object MoshiUtils { val moshi: Moshi by lazy { Moshi.Builder() .build() } }

A fast dirty solution would be just changeing it to var so that we could modify it and add adapters.

Improve auto type (List or Object) for CSV to JSON conversion

In RetrosheetInterceptor.isReturnTypeList(), I see we are using contains("java.util.List") to check whether the return type is in list type or not. This limits auto type to just List but not the other collection type like Set, Array.

Besides, suspend function does not return a list but a kotlin.coroutines.Continuation param is appended at the last of the parameter list. It seems to me that the code cannot check suspend function.

Using contains also dangerous because if a generic type (not collection) accepts List as the type param, contains will cause incorrect. For example:

fun foo(): Foo<Bar<List<String>>>

One more problem (although we don't expect to use retrosheet for a real product), obfuscation may rename List to anything..

I suggest a new check like this:

private fun isSuspendMethodWithListReturnValue(method: Method): Boolean {
    val lastParameter = method.genericParameterTypes.lastOrNull() ?: return false
    val typeName = lastParameter.typeName
    val continuationClassName =
        kotlin.coroutines.Continuation::class.qualifiedName ?: return false
    val isSuspendFun = typeName.startsWith("$continuationClassName<")
    if (!isSuspendFun) {
        return false
    }
    // TODO: We can shorten this with Regex
    val suspendFunReturnType = typeName
        .removePrefix("$continuationClassName<")
        .split("<")
        .first()
        .split(" ")
        .last()
    val clazz = Class.forName(suspendFunReturnType)
    return Collection::class.java.isAssignableFrom(clazz) || clazz.isArray
}

Query gets only first row

I realized simple example for samples.
image
with this table
image

But the query returns only single object which is always first row
image

Can we add the Authorization to this ?

I want to use it for private sheet, or shared the access to only one email. So is there any way we can do that ? Like providing the login details or some sort of cookies or headers, anything into the request.

The Sheet is not working in some devices

I tried using the same sheet in different devices, I get response in not time on Samsung Device, but when trying in MI/Xioami it takes too long and on Vivo device it's not fetching the data. The call is made by the Retrofit Instance

Error when using suspend List<Note>

OkHttp Dispatcher
Process: com.theapache64.notes, PID: 12465
java.lang.NoSuchMethodError: No interface method getTypeName()Ljava/lang/String; in class Ljava/lang/reflect/Type; or its super classes (declaration of 'java.lang.reflect.Type' appears in /system/framework/core-oj.jar)
at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion.isSuspendMethodWithListReturnValue(RetrosheetInterceptor.kt:89)
at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion.isReturnTypeList(RetrosheetInterceptor.kt:66)

Improve `isReturnTypeList` for non suspend method

Currently, as mentioned in #11 , isReturnTypeList can work with any return type which contains java.util.List.
However, this approach has some issues:

  1. Incorrect if the return type is something like this: Foo<Bar<Baz<List<Something>>>
  2. Cannot work with other collection types like Set, Array.

I tried an update like this:

private fun isMethodWithListReturnValue(method: Method): Boolean {
    if (method.returnType.isArray) {
        return true
    }
    val genericReturnType = method.genericReturnType as? ParameterizedType ?: return false
    return isArrayOrCollectionClass(genericReturnType.rawType.typeName)
}

However, this doesn't work if List-type is the secondary type like: Flow<List<Something>>.

If we extract secondary type from Flow like what we did with Continuation for suspend method, we cannot guarantee whether the list is hidden under the third type like Flow<Resource<List<Type>>>.

PatternSyntaxException when deploy on AndroidTV

There is a PatternSyntaxException when compiling to Android TV API 21
E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher java.util.regex.PatternSyntaxException: Syntax error in regexp pattern near index 45: https://docs\.google\.com/spreadsheets/d/(?<docId>.+)/(?<params>.+) ^ at java.util.regex.Pattern.compileImpl(Native Method) at java.util.regex.Pattern.compile(Pattern.java:411) at java.util.regex.Pattern.<init>(Pattern.java:394) at java.util.regex.Pattern.compile(Pattern.java:381) at kotlin.text.Regex.<init>(Regex.kt:89) at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion$URL_REGEX$2.invoke(RetrosheetInterceptor.kt:48) at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion$URL_REGEX$2.invoke(RetrosheetInterceptor.kt:47) at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74) at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion.getURL_REGEX(RetrosheetInterceptor.kt:47) at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion.access$getURL_REGEX(RetrosheetInterceptor.kt:38) at com.github.theapache64.retrosheet.RetrosheetInterceptor.getModifiedRequest(RetrosheetInterceptor.kt:262) at com.github.theapache64.retrosheet.RetrosheetInterceptor.getRetrosheetResponse(RetrosheetInterceptor.kt:179) at com.github.theapache64.retrosheet.RetrosheetInterceptor.intercept(RetrosheetInterceptor.kt:169) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142) at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117) at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229) at okhttp3.RealCall$AsyncCall.execute(RealCall.java:172) at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) at java.lang.Thread.run(Thread.java:818)

Edit and delete records

Is it possible to edit and delete records from a sheet with retrosheet?

Google forms support editing response after submission (must be enabled in setting first), so editing might be possible. But I have no idea about deleting records.

@theapache64 have you given any thought on this?

Update README

  • Add @KeyValue and @ReadAsList annotations to README

java.lang.NoSuchMethodError: No interface method getTypeName (API level 25)

Hello, thanks for the library.
Recently I have faced with crash.

Steps to reproduce: add suspend function into ApiInterface

Looks like problem in this code block and method getTypeName not available on Android:

private fun isSuspendMethodWithListReturnValue(method: Method): Boolean {
            val lastParameter = method.genericParameterTypes.lastOrNull() as? ParameterizedType ?: return false
            if (!Continuation::class.java.isAssignableFrom(Class.forName(lastParameter.rawType.typeName))) {
                return false
            }
}

Crash log:

Process: com.theapache64.notes, PID: 29094
    java.lang.NoSuchMethodError: No interface method getTypeName()Ljava/lang/String; in class Ljava/lang/reflect/Type; or its super classes (declaration of 'java.lang.reflect.Type' appears in /system/framework/core-oj.jar)
        at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion.isSuspendMethodWithListReturnValue(RetrosheetInterceptor.kt:89)
        at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion.isReturnTypeList(RetrosheetInterceptor.kt:66)
        at com.github.theapache64.retrosheet.RetrosheetInterceptor$Companion.access$isReturnTypeList(RetrosheetInterceptor.kt:38)
        at com.github.theapache64.retrosheet.RetrosheetInterceptor.getRetrosheetResponse(RetrosheetInterceptor.kt:225)
        at com.github.theapache64.retrosheet.RetrosheetInterceptor.intercept(RetrosheetInterceptor.kt:169)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:142)
        at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.java:117)
        at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:229)
        at okhttp3.RealCall$AsyncCall.execute(RealCall.java:172)
        at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:32)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
        at java.lang.Thread.run(Thread.java:761)

Support nullable methods

Currently, nullable return type throws HttpException (404). See getNote() with nullable return type

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.