📃 Turn Google Spreadsheet to JSON endpoint (for Android and JVM) for FREE (100%)

Overview

retrosheet 📄

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

https://github.com/theapache64/notes

Benefits 🤗

  • No worries about server health (because you're using Google's server 😋 )
  • Rapid response and unlimited bandwidth
  • You can easily migrate to your REST API without any significant code change.
  • You don't have to create an admin panel/dashboard to control the data. You can simply use Google Spreadsheet app (web/mobile).
  • You can use this library to create POC/MVP instantly

Install 🤝

latestVersion

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

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

Usage ⌨️

How to write data ? ✍️

Step 1 : Writing Data To Sheet

Step 2

  • Select response destination and select/create a Google sheet to store the responses.

Step 3

  • Now you can open the sheet and change sheet name and column names if you want. This is just to make the Google sheet table look like a real database table (optional)

I've changed to

Step 4

  • Next, Press the Send button and copy the form link

Step 5

  • Now let's go to our code and create our RetrosheetInterceptor
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_ENDPOINT,
        "https://docs.google.com/forms/d/e/1FAIpQLSdmavg6P4eZTmIu-0M7xF_z-qDCHdpGebX8MGL43HSGAXcd3w/viewform?usp=sf_link" // form link
    )
    .build()

Step 6

  • Next, let's create a normal Retrofit API interface
@Write @POST(ADD_NOTE_ENDPOINT) // form name suspend fun addNote(@Body addNoteRequest: AddNoteRequest): AddNoteRequest } ">
interface NotesApi {

    @Read("SELECT *") 
    @GET("notes") // sheet name
    suspend fun getNotes(): List<Note>

    @Write
    @POST(ADD_NOTE_ENDPOINT) // form name
    suspend fun addNote(@Body addNoteRequest: AddNoteRequest): AddNoteRequest
}
  • @Write : To write data to a sheet

  • @Read : To read data from a sheet.

You can lean more about query language from here : https://developers.google.com/chart/interactive/docs/querylanguage.

NOTE: You can use your column name in the query rather than using column letter such as A,B,C etc.

How to read data ? 📖

Step 7 : Reading data from Sheet

  • We're done configuring the writing part. Now let's finish the reading part. Create/open a google sheet, (it can be either form connected, or a simple Google sheet).

  • Press Share and copy the link

copy-link

Step 8

  • Remove contents after the last forward slash from the copied link.

For example, this

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

would become this

https://docs.google.com/spreadsheets/d/1IcZTH6-g7cZeht_xr82SHJOuJXD_p55QueMrZcnsAvQ/

Step 9

  • Finally, Set the Retrofit or OkHttp's baseUrl with the above link.

baseUrl

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 🌠

TODO ☑️

  • Create an online tool to generate model class from sheet link

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

Author ✍️

  • theapache64
Comments
  • Apply ktlint

    Apply ktlint

    Changes:

    • Apply ktlint as a plugin in build.gradle via ktlint.gradle.
    • Apply auto format with the tool (via ./gradlew ktlintFormat).
    • Fix star import in RetrosheetInterceptor, GoogleFormHelper.
    • Update IDE's configuration to prevent star import.
    • Rename Result.kt -> Resource.kt to be the same name with the only class in that file.
    • Updare README with Contributing section.
    opened by tuanchauict 7
  • Improve auto type (List or Object) for CSV to JSON conversion

    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
    }
    
    opened by tuanchauict 3
  • Refactor CsvConverter

    Refactor CsvConverter

    I reduce the size of .use scope by converting csv reader into iterator then to sequence and we can use map{}. I also cut the mapping if the return value is not a list.

    opened by tuanchauict 1
  • Cyryllic symbols don't writing

    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

    opened by maestrovsmaster 1
  • The Sheet is not working in some devices

    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

    opened by Meet-Miyani 0
  • Correct suspend function which returns array

    Correct suspend function which returns array

    In #12, I was wrong when checking the return value is an array. Class.forName("className") does not return an Array class (class.isArray is always false). With this PR, I try to fix this by checking whether actualTypeArguments.firstOrNull().typeName ends with [].

    Besides, I also update the code for checking Continuation in the return type. Instead of checking from typeName string, we can make it more reliable by comparing it with lastParameter.rawType.typeName.

    opened by tuanchauict 0
  • [ImgBot] Optimize images

    [ImgBot] Optimize images

    opened by imgbot[bot] 0
  • [ImgBot] Optimize images

    [ImgBot] Optimize images

    opened by imgbot[bot] 0
  • What is the limit of this sheet ?

    What is the limit of this sheet ?

    I would like to ask, that whether the sheet is capable of handling more than 100 request a time, because the google website mentions this.

    Reference : https://support.google.com/a/users/answer/9305987 image

    Reference : https://support.google.com/docs/answer/2494822?hl=en&visit_id=636973141922655348-330596984&rd=2 image

    opened by Meet-Miyani 0
  • PatternSyntaxException when deploy on AndroidTV

    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)

    opened by M4NN3 0
  • Can we add the Authorization to this ?

    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.

    opened by Meet-Miyani 0
  • java.lang.NoSuchMethodError: No interface method getTypeName (API level 25)

    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)
    
    opened by egorikftp 0
  • Edit and delete records

    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?

    opened by mddanishansari 1
Releases(2.0.1)
Owner
Sifar
Sifar
Multiplaform kotlin library for calculating text differences. Based on java-diff-utils, supports JVM, JS and native targets.

kotlin-multiplatform-diff This is a port of java-diff-utils to kotlin with multiplatform support. All credit for the implementation goes to original a

Peter Trifanov 51 Jan 3, 2023
Hornox is a fast BSON serializer, deserializer and node extractor for the JVM.

Hornox is a fast, simple-stupid BSON serializer, deserializer and node extractor for the JVM. Features Full implementation of the BSON Specification w

Txture 1 May 12, 2022
Fuzzy string matching for Kotlin (JVM, native, JS, Web Assembly) - port of Fuzzy Wuzzy Python lib

FuzzyWuzzy-Kotlin Fuzzy string matching for Kotlin (JVM, iOS) - fork of the Java fork of of Fuzzy Wuzzy Python lib. For use in on JVM, Android, or Kot

WillowTree, LLC 54 Nov 8, 2022
CloudStorageUtil - An Android library to make sync on Google Cloud Storage easier

gCloud Storage Utils An Android library to make sync on Google Cloud Storage eas

Evolve Asia 0 Jul 13, 2022
An app full of Constantly updating Google Pixel wallpapers

An app full of Stock Google Pixel wallpapers! Download: Found a Wallpaper not on the app? Submit and issue to the Google Pixel Wallpaper repository Sc

Jack Sam 118 Dec 25, 2022
gRPC and protocol buffers for Android, Kotlin, and Java.

Wire “A man got to have a code!” - Omar Little See the project website for documentation and APIs. As our teams and programs grow, the variety and vol

Square 3.9k Dec 31, 2022
General purpose utilities and hash functions for Android and Java (aka java-common)

Essentials Essentials are a collection of general-purpose classes we found useful in many occasions. Beats standard Java API performance, e.g. LongHas

Markus Junginger 1.4k Dec 29, 2022
Access and process various types of personal data in Android with a set of easy, uniform, and privacy-friendly APIs.

PrivacyStreams PrivacyStreams is an Android library for easy and privacy-friendly personal data access and processing. It offers a functional programm

null 269 Dec 1, 2022
A simple and easy to use stopwatch and timer library for android

TimeIt Now with Timer support! A simple and easy to use stopwatch and timer library for android Introduction A stopwatch can be a very important widge

Yashovardhan Dhanania 35 Dec 10, 2022
Trail is a simple logging system for Java and Android. Create logs using the same API and the library will detect automatically in which platform the code is running.

Trail Trail is a simple logging system for Java and Android. Create logs using the same API and the library will detect automatically in which platfor

Mauricio Togneri 13 Aug 29, 2022
General purpose utilities and hash functions for Android and Java (aka java-common)

Essentials Essentials are a collection of general-purpose classes we found useful in many occasions. Beats standard Java API performance, e.g. LongHas

Markus Junginger 1.4k Dec 29, 2022
DiskCache - Simple and readable disk cache for kotlin and android applications

DiskCache Simple and readable disk cache for kotlin and android applications (with journaled lru strategy) This is a simple lru disk cache, based on t

Giovanni Corte 14 Dec 2, 2022
A library for fast and safe delivery of parameters for Activities and Fragments.

MorbidMask - 吸血面具 Read this in other languages: 中文, English, Change Log A library for fast and safe delivery of parameters for Activities and Fragment

Season 67 Mar 29, 2022
Matches incoming and/or outgoing text messages against set rules and sends them over to webhook.

Textmatic If you ever wanted a tool to simply push the SMS (or text messages) from your phone to somewhere remote, this is it. This app matches all in

Float 2 Jan 7, 2022
Command framework built around Kord, built to be robust and scalable, following Kord's convention and design patterns.

Command framework built around Kord, built to be robust and scalable, following Kord's convention and design patterns.

ZeroTwo Bot 4 Jun 15, 2022
a simple cache for android and java

ASimpleCache ASimpleCache 是一个为android制定的 轻量级的 开源缓存框架。轻量到只有一个java文件(由十几个类精简而来)。 1、它可以缓存什么东西? 普通的字符串、JsonObject、JsonArray、Bitmap、Drawable、序列化的java对象,和 b

Michael Yang 3.7k Dec 14, 2022
A lightning fast, transactional, file-based FIFO for Android and Java.

Tape by Square, Inc. Tape is a collection of queue-related classes for Android and Java. QueueFile is a lightning-fast, transactional, file-based FIFO

Square 2.4k Dec 30, 2022
UPnP/DLNA library for Java and Android

Cling EOL: This project is no longer actively maintained, code may be outdated. If you are interested in maintaining and developing this project, comm

4th Line 1.6k Jan 4, 2023
WebSocket & WAMP in Java for Android and Java 8

Autobahn|Java Client library providing WAMP on Java 8 (Netty) and Android, plus (secure) WebSocket for Android. Autobahn|Java is a subproject of the A

Crossbar.io 1.5k Dec 9, 2022