kandi background
Explore Kits

gson | Java serialization/deserialization library | JSON Processing library

 by   google Java Version: gson-parent-2.9.0 License: Apache-2.0

 by   google Java Version: gson-parent-2.9.0 License: Apache-2.0

Download this library from

kandi X-RAY | gson Summary

gson is a Java library typically used in Utilities, JSON Processing applications. gson has no bugs, it has no vulnerabilities, it has build file available, it has a Permissive License and it has high support. You can download it from GitHub.
Gson is a Java library that can be used to convert Java Objects into their JSON representation. It can also be used to convert a JSON string to an equivalent Java object. Gson can work with arbitrary Java objects including pre-existing objects that you do not have source-code of. There are a few open-source projects that can convert Java objects to JSON. However, most of them require that you place Java annotations in your classes; something that you can not do if you do not have access to the source-code. Most also do not fully support the use of Java Generics. Gson considers both of these as very important design goals.
Support
Support
Quality
Quality
Security
Security
License
License
Reuse
Reuse

kandi-support Support

  • gson has a highly active ecosystem.
  • It has 20811 star(s) with 4024 fork(s). There are 747 watchers for this library.
  • There were 1 major release(s) in the last 6 months.
  • There are 480 open issues and 973 have been closed. On average issues are closed in 482 days. There are 152 open pull requests and 0 closed requests.
  • It has a positive sentiment in the developer community.
  • The latest version of gson is gson-parent-2.9.0
gson Support
Best in #JSON Processing
Average in #JSON Processing
gson Support
Best in #JSON Processing
Average in #JSON Processing

quality kandi Quality

  • gson has 0 bugs and 0 code smells.
gson Quality
Best in #JSON Processing
Average in #JSON Processing
gson Quality
Best in #JSON Processing
Average in #JSON Processing

securitySecurity

  • gson has no vulnerabilities reported, and its dependent libraries have no vulnerabilities reported.
  • gson code analysis shows 0 unresolved vulnerabilities.
  • There are 0 security hotspots that need review.
gson Security
Best in #JSON Processing
Average in #JSON Processing
gson Security
Best in #JSON Processing
Average in #JSON Processing

license License

  • gson is licensed under the Apache-2.0 License. This license is Permissive.
  • Permissive licenses have the least restrictions, and you can use them in most projects.
gson License
Best in #JSON Processing
Average in #JSON Processing
gson License
Best in #JSON Processing
Average in #JSON Processing

buildReuse

  • gson releases are available to install and integrate.
  • Build file is available. You can build the component from source.
  • Installation instructions, examples and code snippets are available.
  • gson saves you 13213 person hours of effort in developing the same functionality from scratch.
  • It has 26955 lines of code, 2345 functions and 229 files.
  • It has medium code complexity. Code complexity directly impacts maintainability of the code.
gson Reuse
Best in #JSON Processing
Average in #JSON Processing
gson Reuse
Best in #JSON Processing
Average in #JSON Processing
Top functions reviewed by kandi - BETA

kandi has reviewed gson and discovered the below as its top functions. This is intended to give you an instant insight into gson implemented functionality, and help decide if they suit your requirements.

  • Parse a date from a string .
  • Moves to the next token .
  • Deserialize a message .
  • Returns the node for the specified key .
  • Creates a new object constructor .
  • Excludes the given field .
  • Formats a date into a string .
  • Time objects reflection .
  • Converts a string to a string literal .
  • Writes a string .

gson Key Features

Provide simple toJson() and fromJson() methods to convert Java objects to JSON and vice-versa

Allow pre-existing unmodifiable objects to be converted to and from JSON

Extensive support of Java Generics

Allow custom representations for objects

Support arbitrarily complex objects (with deep inheritance hierarchies and extensive use of generic types)

Download

copy iconCopydownload iconDownload
dependencies {
  implementation 'com.google.code.gson:gson:2.9.0'
}

License

copy iconCopydownload iconDownload
Copyright 2008 Google Inc.

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.

Retrofit OkHttp - "unexpected end of stream"

copy iconCopydownload iconDownload
interface ApiServiceInterface {

    companion object Factory{

        fun create(): ApiServiceInterface {
            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY
            
            val stringInterceptor = Interceptor { chain: Interceptor.Chain ->
                val request = chain.request()
                val response = chain.proceed(request)
                val source = response.body()?.source()
                source?.request(Long.MAX_VALUE)
                val buffer = source?.buffer()
                var responseString = buffer?.clone()?.readString(Charset.forName("UTF-8"))
                if (responseString != null && responseString.length > 2) {
                    val lastTwo = responseString.takeLast(2)
                    if (lastTwo != "}}") {
                        val lastOne = responseString.takeLast(1)
                        responseString = if (lastOne != "}") {
                            "$responseString}}"
                        } else {
                            "$responseString}"
                        }
                    }
                }
                val contentType = response.body()?.contentType()
                val body = ResponseBody.create(contentType, responseString ?: "")
                return@Interceptor response.newBuilder().body(body).build()
            }

            val client = OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30,TimeUnit.SECONDS)
                .addInterceptor(interceptor)
                .addInterceptor(stringInterceptor)
                .retryOnConnectionFailure(true)
                .connectionPool(ConnectionPool(0, 5, TimeUnit.MINUTES))
                .protocols(listOf(Protocol.HTTP_1_1))
                .build()
            val gson = GsonBuilder().create()
            val retrofit = Retrofit.Builder()
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addConverterFactory(ScalarsConverterFactory.create())
                .baseUrl("http://3.124.6.203:5000")
                .client(client)
                .build()
            return retrofit.create(ApiServiceInterface::class.java)
        }
    }


    @Headers("Content-type: application/json", "Connection: close", "Accept-Encoding: identity")
    @POST("/")
    fun requestAsync(@Body data: JsonObject): Deferred<Response>
}

Caused by java.lang.RuntimeException: Missing type parameter

copy iconCopydownload iconDownload
return GsonBuilder().create().fromJson(
    json,
    object : TypeToken<List<Question>?>() {}.type
)
return GsonBuilder().create().fromJson(
    json,
    TypeToken.getParameterized(List::class.java, Question::class.java).type
)
-----------------------
return GsonBuilder().create().fromJson(
    json,
    object : TypeToken<List<Question>?>() {}.type
)
return GsonBuilder().create().fromJson(
    json,
    TypeToken.getParameterized(List::class.java, Question::class.java).type
)
-----------------------
# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.
-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken
-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken

android:exported needs to be explicitly specified for &lt;activity&gt;. Apps targeting Android 12 and higher are required to specify

copy iconCopydownload iconDownload
<receiver android:name="<name_of_the_entry>"
                android:exported="false or true"
                tools:node="merge" />
-----------------------
     <activity
                android:name="<activity which is giving error>"
                android:exported="true"
                tools:node="merge" />
-----------------------
<activity android:name="name_of_the_activity_inside_library>"
    android:exported="false|true"
    tools:node="merge" />
-----------------------
debugImplementation "androidx.fragment:fragment-testing:<version>"
androidTestImplementation "androidx.fragment:fragment-testing:<version>"
-----------------------
debugImplementation "androidx.fragment:fragment-testing:<version>"
androidTestImplementation "androidx.fragment:fragment-testing:<version>"
-----------------------
 android:exported="true"
-----------------------
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.0.4"
-----------------------
> java.util.concurrent.ExecutionException: com.android.builder.testing.api.DeviceException: com.android.ddmlib.InstallException: INSTALL_PARSE_FAILED_MANIFEST_MALFORMED: Failed parse during installPackageLI: /data/app/xxxxx.tmp/base.apk (at Binary XML file line #129): YOUR.FULLY.QUALIFIED.NAME.FAILING.ACTIVITY: Targeting S+ (version 31 and above) requires that an explicit value for android:exported be defined when intent filters are present
-----------------------
<activity android:name="androidx.test.core.app.InstrumentationActivityInvoker$BootstrapActivity"
android:exported="true"
tools:node="merge"/>

<activity android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
android:exported="true"
tools:node="merge"/>

<activity android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyFloatingActivity"
android:exported="true"
tools:node="merge"/>
-----------------------
 <activity
        android:name="com.test.activity.SplashActivity"
        android:clearTaskOnLaunch="true"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:noHistory="true"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme.NoActionBar"
        android:exported="true">
   <receiver
        android:name="com.test.receiver.ShareReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="com.test.fcm.android.action.broadcast" />
        </intent-filter>
    </receiver>

   <service
        android:name="com.google.android.gms.tagmanager.InstallReferrerService"
        android:exported="true" />
testImplementation 'junit:junit:4.13.2' 
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.razorpay:checkout:1.6.15'
-----------------------
 <activity
        android:name="com.test.activity.SplashActivity"
        android:clearTaskOnLaunch="true"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:noHistory="true"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme.NoActionBar"
        android:exported="true">
   <receiver
        android:name="com.test.receiver.ShareReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="com.test.fcm.android.action.broadcast" />
        </intent-filter>
    </receiver>

   <service
        android:name="com.google.android.gms.tagmanager.InstallReferrerService"
        android:exported="true" />
testImplementation 'junit:junit:4.13.2' 
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.razorpay:checkout:1.6.15'
-----------------------
 <activity
        android:name="com.test.activity.SplashActivity"
        android:clearTaskOnLaunch="true"
        android:label="@string/app_name"
        android:launchMode="singleTop"
        android:noHistory="true"
        android:screenOrientation="portrait"
        android:theme="@style/AppTheme.NoActionBar"
        android:exported="true">
   <receiver
        android:name="com.test.receiver.ShareReceiver"
        android:exported="true">
        <intent-filter>
            <action android:name="com.test.fcm.android.action.broadcast" />
        </intent-filter>
    </receiver>

   <service
        android:name="com.google.android.gms.tagmanager.InstallReferrerService"
        android:exported="true" />
testImplementation 'junit:junit:4.13.2' 
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'com.razorpay:checkout:1.6.15'
-----------------------
"com.google.dagger:hilt-android:2.38.1"
"com.google.dagger:hilt-android-gradle-plugin:2.38.1"
"com.google.dagger:hilt-android-compiler:2.38.1"
"com.google.dagger:hilt-android-testing:2.38.1"
"com.google.dagger:hilt-android:2.40.5"
"com.google.dagger:hilt-android-gradle-plugin:2.40.5"
"com.google.dagger:hilt-android-compiler:2.40.5"
"com.google.dagger:hilt-android-testing:2.40.5" 
-----------------------
"com.google.dagger:hilt-android:2.38.1"
"com.google.dagger:hilt-android-gradle-plugin:2.38.1"
"com.google.dagger:hilt-android-compiler:2.38.1"
"com.google.dagger:hilt-android-testing:2.38.1"
"com.google.dagger:hilt-android:2.40.5"
"com.google.dagger:hilt-android-gradle-plugin:2.40.5"
"com.google.dagger:hilt-android-compiler:2.40.5"
"com.google.dagger:hilt-android-testing:2.40.5" 
-----------------------
<receiver
    android:name="com.razorpay.RzpTokenReceiver"
    android:exported="false">
    <intent-filter>
        <action android:name="rzp.device_token.share" />
    </intent-filter>
</receiver>

<activity
    android:name="com.razorpay.CheckoutActivity"
    android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
    android:exported="true"
    android:theme="@style/CheckoutTheme">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <data
            android:host="rzp.io"
            android:scheme="io.rzp" />
    </intent-filter>
</activity>
-----------------------
android:exported="true" <!-- or false as required -->
-----------------------
<activity
     android:name=".MainActivity"
     android:exported="true" <** add this line on AndroidManifest.xml**
     android:launchMode="singleTop"
     android:theme="@style/LaunchTheme"
 </activity>

Android Build Error: &quot;lStar not found...&quot;

copy iconCopydownload iconDownload
compileSdk = 31

classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10'
-----------------------
implementation 'androidx.appcompat:appcompat:1.3.0'
-----------------------
configurations.all {
        resolutionStrategy.force 'androidx.appcompat:appcompat:1.3.0'
    }

Could not resolve com.google.guava:guava:30.1-jre - Gradle project sync failed. Basic functionality will not work properly - in kotlin project

copy iconCopydownload iconDownload
    repositories {
        mavenCentral()
        google()
    }

-----------------------
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        mavenCentral()
        google()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:7.1.1'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        mavenCentral()
        google()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

Retrofit2, how I can convert response with diferent object names but same data types

copy iconCopydownload iconDownload
data class DetailModel(val id: String = "", val success: Boolean, val cache_last_updated: Long, val total: Int) 

interface apiService {
    @GET("....")
    fun getWalletResume( listAddress: List<String> ): Call<Map<String, DetailModel>>
}
val json = """
    {
      "0x1234": {
        "success": true,
        "cache_last_updated": 1642815869695,
        "total": 500
      },
      "0x1244445": {
        "success": true,
        "cache_last_updated": 1642815869695,
        "total": 324
      },
      "0x47851": {
        "success": true,
        "cache_last_updated": 1642815869695,
        "total": 324
      }
    }
""".trimIndent()


data class DetailModel(val id: String = "", val success: Boolean, val cache_last_updated: Long, val total: Int)

fun main() {
    val gson = Gson()
    val type: Type = object : TypeToken<Map<String, DetailModel>?>() {}.type
    
    // step 1. simulates the retrofit call
    val mapResult: Map<String, DetailModel> = gson.fromJson(json, type)

    // step 2
    val result = mapResult.map { entry -> entry.value.copy(id = entry.key) }

    println(result) 
    // [DetailModel(id=0x1234, success=true, cache_last_updated=1642815869695, total=500), DetailModel(id=0x1244445, success=true, cache_last_updated=1642815869695, total=324), DetailModel(id=0x47851, success=true, cache_last_updated=1642815869695, total=324)]
}

-----------------------
data class DetailModel(val id: String = "", val success: Boolean, val cache_last_updated: Long, val total: Int) 

interface apiService {
    @GET("....")
    fun getWalletResume( listAddress: List<String> ): Call<Map<String, DetailModel>>
}
val json = """
    {
      "0x1234": {
        "success": true,
        "cache_last_updated": 1642815869695,
        "total": 500
      },
      "0x1244445": {
        "success": true,
        "cache_last_updated": 1642815869695,
        "total": 324
      },
      "0x47851": {
        "success": true,
        "cache_last_updated": 1642815869695,
        "total": 324
      }
    }
""".trimIndent()


data class DetailModel(val id: String = "", val success: Boolean, val cache_last_updated: Long, val total: Int)

fun main() {
    val gson = Gson()
    val type: Type = object : TypeToken<Map<String, DetailModel>?>() {}.type
    
    // step 1. simulates the retrofit call
    val mapResult: Map<String, DetailModel> = gson.fromJson(json, type)

    // step 2
    val result = mapResult.map { entry -> entry.value.copy(id = entry.key) }

    println(result) 
    // [DetailModel(id=0x1234, success=true, cache_last_updated=1642815869695, total=500), DetailModel(id=0x1244445, success=true, cache_last_updated=1642815869695, total=324), DetailModel(id=0x47851, success=true, cache_last_updated=1642815869695, total=324)]
}

-----------------------
@GET("....")
suspend fun getWalletDetails(...): Map<String, WalletDetailDTO>
val mapOfWalletDetails = apiService.getWalletResume(...)

val walletDetailCollection: Collection<WalletDetailDTO> = mapOfWalletDetails.values()
-----------------------
@GET("....")
suspend fun getWalletDetails(...): Map<String, WalletDetailDTO>
val mapOfWalletDetails = apiService.getWalletResume(...)

val walletDetailCollection: Collection<WalletDetailDTO> = mapOfWalletDetails.values()

using webclient to call the grapql mutation API in spring boot

copy iconCopydownload iconDownload
graphQlBody = { "query" : mutation_query, "variables" : { "eventLog" : event_log_json } }
public Mono<String> sendLogsToGraphQL(Map<String,Object> body){
    return webClient
            .post()
            .uri("http://localhost:8080/logs/createEventLog")
            .bodyValue(BodyInserters.fromValue(body))
            .retrieve()
            .bodyToMono(String.class);
}  
-----------------------
graphQlBody = { "query" : mutation_query, "variables" : { "eventLog" : event_log_json } }
public Mono<String> sendLogsToGraphQL(Map<String,Object> body){
    return webClient
            .post()
            .uri("http://localhost:8080/logs/createEventLog")
            .bodyValue(BodyInserters.fromValue(body))
            .retrieve()
            .bodyToMono(String.class);
}  

How to build the resultant docker image file directory?

copy iconCopydownload iconDownload
image="testing"
targetDir="target"
lowerDir="$(docker inspect --format "{{ .GraphDriver.Data.LowerDir }}" $image)"
upperDir="$(docker inspect --format "{{ .GraphDriver.Data.UpperDir }}" $image)"
workDir="$(docker inspect --format "{{ .GraphDriver.Data.WorkDir }}" $image)"
mergedDir="/mnt/$targetDir"

sudo mkdir "$mergedDir"
echo "mounting in $mergedDir ..."
sudo mount -t overlay -o lowerdir=$lowerDir,upperdir=$upperDir,workdir=$workDir overlay "$mergedDir"
echo "copying $mergedDir in $targetDir/ ..."
sudo cp -a "$mergedDir/." "$targetDir/" 
echo "unmounting $mergedDir ..."
sudo umount overlay
sudo rmdir "$mergedDir"
docker create your_image sh
docker cp your_container_name:/ custom_directory
docker inspect --format "{{ json .GraphDriver.Data }}" container_name
skopeo copy docker-daemon:your_image:your_tag "dir:$(pwd)/dir_for_layers"
-----------------------
image="testing"
targetDir="target"
lowerDir="$(docker inspect --format "{{ .GraphDriver.Data.LowerDir }}" $image)"
upperDir="$(docker inspect --format "{{ .GraphDriver.Data.UpperDir }}" $image)"
workDir="$(docker inspect --format "{{ .GraphDriver.Data.WorkDir }}" $image)"
mergedDir="/mnt/$targetDir"

sudo mkdir "$mergedDir"
echo "mounting in $mergedDir ..."
sudo mount -t overlay -o lowerdir=$lowerDir,upperdir=$upperDir,workdir=$workDir overlay "$mergedDir"
echo "copying $mergedDir in $targetDir/ ..."
sudo cp -a "$mergedDir/." "$targetDir/" 
echo "unmounting $mergedDir ..."
sudo umount overlay
sudo rmdir "$mergedDir"
docker create your_image sh
docker cp your_container_name:/ custom_directory
docker inspect --format "{{ json .GraphDriver.Data }}" container_name
skopeo copy docker-daemon:your_image:your_tag "dir:$(pwd)/dir_for_layers"
-----------------------
image="testing"
targetDir="target"
lowerDir="$(docker inspect --format "{{ .GraphDriver.Data.LowerDir }}" $image)"
upperDir="$(docker inspect --format "{{ .GraphDriver.Data.UpperDir }}" $image)"
workDir="$(docker inspect --format "{{ .GraphDriver.Data.WorkDir }}" $image)"
mergedDir="/mnt/$targetDir"

sudo mkdir "$mergedDir"
echo "mounting in $mergedDir ..."
sudo mount -t overlay -o lowerdir=$lowerDir,upperdir=$upperDir,workdir=$workDir overlay "$mergedDir"
echo "copying $mergedDir in $targetDir/ ..."
sudo cp -a "$mergedDir/." "$targetDir/" 
echo "unmounting $mergedDir ..."
sudo umount overlay
sudo rmdir "$mergedDir"
docker create your_image sh
docker cp your_container_name:/ custom_directory
docker inspect --format "{{ json .GraphDriver.Data }}" container_name
skopeo copy docker-daemon:your_image:your_tag "dir:$(pwd)/dir_for_layers"
-----------------------
image="testing"
targetDir="target"
lowerDir="$(docker inspect --format "{{ .GraphDriver.Data.LowerDir }}" $image)"
upperDir="$(docker inspect --format "{{ .GraphDriver.Data.UpperDir }}" $image)"
workDir="$(docker inspect --format "{{ .GraphDriver.Data.WorkDir }}" $image)"
mergedDir="/mnt/$targetDir"

sudo mkdir "$mergedDir"
echo "mounting in $mergedDir ..."
sudo mount -t overlay -o lowerdir=$lowerDir,upperdir=$upperDir,workdir=$workDir overlay "$mergedDir"
echo "copying $mergedDir in $targetDir/ ..."
sudo cp -a "$mergedDir/." "$targetDir/" 
echo "unmounting $mergedDir ..."
sudo umount overlay
sudo rmdir "$mergedDir"
docker create your_image sh
docker cp your_container_name:/ custom_directory
docker inspect --format "{{ json .GraphDriver.Data }}" container_name
skopeo copy docker-daemon:your_image:your_tag "dir:$(pwd)/dir_for_layers"
-----------------------
image="testing"
targetDir="target"
lowerDir="$(docker inspect --format "{{ .GraphDriver.Data.LowerDir }}" $image)"
upperDir="$(docker inspect --format "{{ .GraphDriver.Data.UpperDir }}" $image)"
workDir="$(docker inspect --format "{{ .GraphDriver.Data.WorkDir }}" $image)"
mergedDir="/mnt/$targetDir"

sudo mkdir "$mergedDir"
echo "mounting in $mergedDir ..."
sudo mount -t overlay -o lowerdir=$lowerDir,upperdir=$upperDir,workdir=$workDir overlay "$mergedDir"
echo "copying $mergedDir in $targetDir/ ..."
sudo cp -a "$mergedDir/." "$targetDir/" 
echo "unmounting $mergedDir ..."
sudo umount overlay
sudo rmdir "$mergedDir"
docker create your_image sh
docker cp your_container_name:/ custom_directory
docker inspect --format "{{ json .GraphDriver.Data }}" container_name
skopeo copy docker-daemon:your_image:your_tag "dir:$(pwd)/dir_for_layers"
-----------------------
# create container from image without starting it
docker create --name my_container my_image

# export container file system to tar archive
docker export -o my_image.tar my_container

# or directly extract the archive in the current folder
docker export my_container | tar -x

# clean up temporary container
docker rm my_container
-----------------------
FROM busybox as build
COPY ./source.tgz /app/source.tgz
WORKDIR /app
RUN tar -xzf ./source.tgz \
 && ./install.sh
RUN rm ./source.tgz 

FROM busybox as release
COPY --from=build /app /app
-----------------------
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <groupId>gd.wa</groupId>
  <artifactId>minimal-pom</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <modelVersion>4.0.0</modelVersion>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-compress -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-compress</artifactId>
      <version>1.21</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.9</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.11.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.13</version>
    </dependency>
  </dependencies>

  <properties>
    <java.version>1.8</java.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>
package com.sonatype.docker.poc;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import com.google.gson.Gson;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class Main
{
  static class TokenResponse
  {
    String access_token;
  }

  static class Layer
  {
    String digest;

    long size;
  }

  static class ManifestResponse
  {
    List<Layer> layers;
  }

  static String imageName = "httpd";

  static String repoName = "library";

  static String base64EncodedUsernamePassword = "username:password[base64-encoded]";

  public static void main(String[] args) throws Exception {
    try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
      // Get a token
      HttpGet httpget = new HttpGet(
          "https://auth.docker.io/token?service=registry.docker.io&scope=repository:" + repoName + "/" + imageName +
              ":pull");
      httpget.setHeader("Authorization", "Basic " + base64EncodedUsernamePassword);

      CloseableHttpResponse response = httpclient.execute(httpget);
      String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8.name());
      String token = new Gson().fromJson(text, TokenResponse.class).access_token;

      // Get the list of layers
      httpget = new HttpGet("https://registry-1.docker.io/v2/" + repoName + "/" + imageName + "/manifests/latest");
      httpget.setHeader("Authorization", "Bearer " + token);
      httpget.setHeader("Accept", "application/vnd.docker.distribution.manifest.v2+json");

      response = httpclient.execute(httpget);
      text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8.name());
      ManifestResponse manifestResponse = new Gson().fromJson(text, ManifestResponse.class);

      List<File> files = new ArrayList<>();

      int counter = 100;

      // Download layers (tar files)
      for (Layer layer : manifestResponse.layers) {
        System.out.println("Downloading layer: " + layer.digest);
        System.out.println("Layer size:" + layer.size + " bytes.(" + ((double) layer.size) / 1_000_000 + " MB)");
        httpget =
            new HttpGet("https://registry-1.docker.io/v2/" + repoName + "/" + imageName + "/blobs/" + layer.digest);
        httpget.setHeader("Authorization", "Bearer " + token);
        httpget.setHeader("Accept", "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip");

        response = httpclient.execute(httpget);
        File targetFile = new File(counter + "_" + layer.digest.substring(7, 17) + ".tar");
        counter++;
        files.add(targetFile);

        InputStream inputStream = response.getEntity().getContent();
        OutputStream outputStream = new FileOutputStream(targetFile);
        IOUtils.copy(inputStream, outputStream);
        inputStream.close();
        outputStream.close();
      }

      unTarGz(files);
    }
  }

  public static void unTarGz(List<File> files) throws IOException {
    for (File file : files) {
      TarArchiveInputStream tararchiveinputstream =
          new TarArchiveInputStream(
              new GzipCompressorInputStream(
                  new BufferedInputStream(Files.newInputStream(file.toPath()))));

      ArchiveEntry archiveentry;
      while ((archiveentry = tararchiveinputstream.getNextEntry()) != null) {
        Path pathEntryOutput = Paths.get("./fs").resolve(archiveentry.getName());
        if (archiveentry.isDirectory()) {
          if (!Files.exists(pathEntryOutput)) {
            Files.createDirectory(pathEntryOutput);
          }
        }
        else {
          if (archiveentry.getName().contains(".wh")) {
            try {
              Files.delete(Paths.get("./fs").resolve(archiveentry.getName().replace(".wh.", "")));
            }
            catch (NoSuchFileException noSuchFileException) {
              noSuchFileException.printStackTrace();
            }
          }
          else {
            if (Files.exists(pathEntryOutput)) {
              Files.delete(pathEntryOutput);
            }
            Files.copy(tararchiveinputstream, pathEntryOutput);
          }
        }
      }

      tararchiveinputstream.close();
    }
  }
}
-----------------------
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <groupId>gd.wa</groupId>
  <artifactId>minimal-pom</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <modelVersion>4.0.0</modelVersion>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-compress -->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-compress</artifactId>
      <version>1.21</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
    <dependency>
      <groupId>com.google.code.gson</groupId>
      <artifactId>gson</artifactId>
      <version>2.8.9</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
    <dependency>
      <groupId>commons-io</groupId>
      <artifactId>commons-io</artifactId>
      <version>2.11.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpclient -->
    <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpclient</artifactId>
      <version>4.5.13</version>
    </dependency>
  </dependencies>

  <properties>
    <java.version>1.8</java.version>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>
package com.sonatype.docker.poc;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import com.google.gson.Gson;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class Main
{
  static class TokenResponse
  {
    String access_token;
  }

  static class Layer
  {
    String digest;

    long size;
  }

  static class ManifestResponse
  {
    List<Layer> layers;
  }

  static String imageName = "httpd";

  static String repoName = "library";

  static String base64EncodedUsernamePassword = "username:password[base64-encoded]";

  public static void main(String[] args) throws Exception {
    try (CloseableHttpClient httpclient = HttpClients.createDefault()) {
      // Get a token
      HttpGet httpget = new HttpGet(
          "https://auth.docker.io/token?service=registry.docker.io&scope=repository:" + repoName + "/" + imageName +
              ":pull");
      httpget.setHeader("Authorization", "Basic " + base64EncodedUsernamePassword);

      CloseableHttpResponse response = httpclient.execute(httpget);
      String text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8.name());
      String token = new Gson().fromJson(text, TokenResponse.class).access_token;

      // Get the list of layers
      httpget = new HttpGet("https://registry-1.docker.io/v2/" + repoName + "/" + imageName + "/manifests/latest");
      httpget.setHeader("Authorization", "Bearer " + token);
      httpget.setHeader("Accept", "application/vnd.docker.distribution.manifest.v2+json");

      response = httpclient.execute(httpget);
      text = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8.name());
      ManifestResponse manifestResponse = new Gson().fromJson(text, ManifestResponse.class);

      List<File> files = new ArrayList<>();

      int counter = 100;

      // Download layers (tar files)
      for (Layer layer : manifestResponse.layers) {
        System.out.println("Downloading layer: " + layer.digest);
        System.out.println("Layer size:" + layer.size + " bytes.(" + ((double) layer.size) / 1_000_000 + " MB)");
        httpget =
            new HttpGet("https://registry-1.docker.io/v2/" + repoName + "/" + imageName + "/blobs/" + layer.digest);
        httpget.setHeader("Authorization", "Bearer " + token);
        httpget.setHeader("Accept", "application/vnd.docker.image.rootfs.foreign.diff.tar.gzip");

        response = httpclient.execute(httpget);
        File targetFile = new File(counter + "_" + layer.digest.substring(7, 17) + ".tar");
        counter++;
        files.add(targetFile);

        InputStream inputStream = response.getEntity().getContent();
        OutputStream outputStream = new FileOutputStream(targetFile);
        IOUtils.copy(inputStream, outputStream);
        inputStream.close();
        outputStream.close();
      }

      unTarGz(files);
    }
  }

  public static void unTarGz(List<File> files) throws IOException {
    for (File file : files) {
      TarArchiveInputStream tararchiveinputstream =
          new TarArchiveInputStream(
              new GzipCompressorInputStream(
                  new BufferedInputStream(Files.newInputStream(file.toPath()))));

      ArchiveEntry archiveentry;
      while ((archiveentry = tararchiveinputstream.getNextEntry()) != null) {
        Path pathEntryOutput = Paths.get("./fs").resolve(archiveentry.getName());
        if (archiveentry.isDirectory()) {
          if (!Files.exists(pathEntryOutput)) {
            Files.createDirectory(pathEntryOutput);
          }
        }
        else {
          if (archiveentry.getName().contains(".wh")) {
            try {
              Files.delete(Paths.get("./fs").resolve(archiveentry.getName().replace(".wh.", "")));
            }
            catch (NoSuchFileException noSuchFileException) {
              noSuchFileException.printStackTrace();
            }
          }
          else {
            if (Files.exists(pathEntryOutput)) {
              Files.delete(pathEntryOutput);
            }
            Files.copy(tararchiveinputstream, pathEntryOutput);
          }
        }
      }

      tararchiveinputstream.close();
    }
  }
}

Webflux, with Websocket how to prevent subscribing twice of reactive redis messaging operation

copy iconCopydownload iconDownload
data -> session.textMessage(gson.toJson(data))

Library to change to fix Google app publish error; Billing Library version 3 in android studio

copy iconCopydownload iconDownload
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.android.billingclient:billing:4.0.0'
<string name="license_key">Place Your ID Here</string>
<string name="product_id">android.test.purchased</string>
import android.app.Activity
import android.content.Context
import android.widget.Toast
import com.android.billingclient.api.*
import java.io.IOException
import java.util.ArrayList

class InAppPurchase(context: Context) : PurchasesUpdatedListener{
    private val mContext: Context = context
    private var billingClient: BillingClient
    var ackPurchase =
        AcknowledgePurchaseResponseListener { billingResult ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                //if purchase is acknowledged
                // Grant entitlement to the user. and restart activity
                SharedPreferencesUtils.setPurchasedBillingValue(mContext, true)
                showMessage("Item Purchased")
                (mContext as Activity).recreate()
            }
        }

    init {
        billingClient =
            BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    val queryPurchase = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                    val queryPurchases = queryPurchase.purchasesList
                    if (queryPurchases != null && queryPurchases.size > 0) {
                        handlePurchases(queryPurchases)
                    } else {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext, false)
                    }
                }
            }

            override fun onBillingServiceDisconnected() {}
        })
    }

    fun productPurchase() {
        //check if service is already connected
        if (billingClient.isReady) {
            initiatePurchase()
        } else {
            billingClient =
                BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
            billingClient.startConnection(object : BillingClientStateListener {
                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                        initiatePurchase()
                    } else {
                        showMessage("Error" + billingResult.debugMessage)
                    }
                }

                override fun onBillingServiceDisconnected() {}
            })
        }
    }

    private fun initiatePurchase() {
        val skuList: MutableList<String> = ArrayList()
        skuList.add(mContext.resources.getString(R.string.product_id))
        val params = SkuDetailsParams.newBuilder()
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
        billingClient.querySkuDetailsAsync(
            params.build()
        ) { billingResult, skuDetailsList ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                if (skuDetailsList != null && skuDetailsList.size > 0) {
                    val flowParams = BillingFlowParams.newBuilder()
                        .setSkuDetails(skuDetailsList[0])
                        .build()
                    billingClient.launchBillingFlow(mContext as Activity, flowParams)
                } else {
                    //try to add item/product id "purchase" inside managed product in google play console
                    showMessage("Purchase Item not Found")
                }
            } else {
                showMessage(" Error " + billingResult.debugMessage)
            }
        }
    }

    /**
     * Verifies that the purchase was signed correctly for this developer's public key.
     *
     * Note: It's strongly recommended to perform such check on your backend since hackers can
     * replace this method with "constant true" if they decompile/rebuild your app.
     *
     */
    private fun verifyValidSignature(signedData: String, signature: String): Boolean {
        return try {
            // To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
            val base64Key = mContext.getString(R.string.license_key)
            Security.verifyPurchase(base64Key, signedData, signature)
        } catch (e: IOException) {
            false
        }
    }

    fun handlePurchases(purchases: List<Purchase>) {
        for (purchase in purchases) {
            //if item is purchased
            if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                if (!verifyValidSignature(purchase.originalJson, purchase.signature)) {
                    // Invalid purchase
                    // show error to user
                   showMessage("Invalid Purchase")
                    return
                }
//                 else purchase is valid
//                if item is purchased and not acknowledged
                if (!purchase.isAcknowledged) {
                    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.purchaseToken)
                        .build()
                    billingClient.acknowledgePurchase(acknowledgePurchaseParams, ackPurchase)
                } else {
                    // Grant entitlement to the user on item purchase
                    // restart activity
                    if (!SharedPreferencesUtils.getPurchasedBillingValue(mContext)) {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext,true)
                        showMessage("Item Purchased")
                        (mContext as Activity).recreate()
                    }
                }
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PENDING) {
                showMessage("Purchase is Pending. Please complete Transaction")
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {
                SharedPreferencesUtils.setPurchasedBillingValue(mContext,false)
                showMessage("Purchase Status Unknown")
            }
        }


    }


    private fun showMessage(message: String) {
        (mContext as Activity).runOnUiThread{
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
        }

    }

    override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
        //if item newly purchased
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            handlePurchases(purchases)
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            val queryAlreadyPurchasesResult =
                billingClient.queryPurchases(BillingClient.SkuType.INAPP)
            val alreadyPurchases = queryAlreadyPurchasesResult.purchasesList
            alreadyPurchases?.let { handlePurchases(it) }
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            showMessage("Purchase Canceled")
        } else {
            showMessage("Error updated" + billingResult.debugMessage)
        }
    }


    fun onDestroyBilling() {
        billingClient.endConnection()
    }

}
import android.text.TextUtils
import android.util.Base64
import java.io.IOException
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec

/**
 * Security-related methods. For a secure implementation, all of this code should be implemented on
 * a server that communicates with the application on the device.
 */
object Security {

    private const val KEY_FACTORY_ALGORITHM = "RSA"
    private const val SIGNATURE_ALGORITHM = "SHA1withRSA"

    /**
     * Verifies that the data was signed with the given signature, and returns the verified
     * purchase.
     * @param base64PublicKey the base64-encoded public key to use for verifying.
     * @param signedData the signed JSON string (signed, not encrypted)
     * @param signature the signature for the data, signed with the private key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun verifyPurchase(
        base64PublicKey: String, signedData: String,
        signature: String
    ): Boolean {
        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
            || TextUtils.isEmpty(signature)
        ) {
            //Purchase verification failed: missing data
            return false
        }
        val key = generatePublicKey(base64PublicKey)
        return verify(key, signedData, signature)
    }

    /**
     * Generates a PublicKey instance from a string containing the Base64-encoded public key.
     *
     * @param encodedPublicKey Base64-encoded public key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun generatePublicKey(encodedPublicKey: String): PublicKey {
        return try {
            val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
            val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
            keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available.
            throw RuntimeException(e)
        } catch (e: InvalidKeySpecException) {
            val msg = "Invalid key specification: $e"
            throw IOException(msg)
        }
    }

    /**
     * Verifies that the signature from the server matches the computed signature on the data.
     * Returns true if the data is correctly signed.
     *
     * @param publicKey public key associated with the developer account
     * @param signedData signed data from server
     * @param signature server signature
     * @return true if the data and signature match
     */
    private fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean {
        val signatureBytes: ByteArray = try {
            Base64.decode(signature, Base64.DEFAULT)
        } catch (e: IllegalArgumentException) {
            //Base64 decoding failed
            return false
        }
        try {
            val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM)
            signatureAlgorithm.initVerify(publicKey)
            signatureAlgorithm.update(signedData.toByteArray())
            return signatureAlgorithm.verify(signatureBytes)
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available
            throw RuntimeException(e)
        } catch (e: InvalidKeyException) {
            //Invalid key specification
        } catch (e: SignatureException) {
            //Signature exception
        }
        return false
    }

}
var inAppPurchase: InAppPurchase  = InAppPurchase(this)
btnInApp.setOnClickListener {
            inAppPurchase.productPurchase()
        }
-----------------------
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.android.billingclient:billing:4.0.0'
<string name="license_key">Place Your ID Here</string>
<string name="product_id">android.test.purchased</string>
import android.app.Activity
import android.content.Context
import android.widget.Toast
import com.android.billingclient.api.*
import java.io.IOException
import java.util.ArrayList

class InAppPurchase(context: Context) : PurchasesUpdatedListener{
    private val mContext: Context = context
    private var billingClient: BillingClient
    var ackPurchase =
        AcknowledgePurchaseResponseListener { billingResult ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                //if purchase is acknowledged
                // Grant entitlement to the user. and restart activity
                SharedPreferencesUtils.setPurchasedBillingValue(mContext, true)
                showMessage("Item Purchased")
                (mContext as Activity).recreate()
            }
        }

    init {
        billingClient =
            BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    val queryPurchase = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                    val queryPurchases = queryPurchase.purchasesList
                    if (queryPurchases != null && queryPurchases.size > 0) {
                        handlePurchases(queryPurchases)
                    } else {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext, false)
                    }
                }
            }

            override fun onBillingServiceDisconnected() {}
        })
    }

    fun productPurchase() {
        //check if service is already connected
        if (billingClient.isReady) {
            initiatePurchase()
        } else {
            billingClient =
                BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
            billingClient.startConnection(object : BillingClientStateListener {
                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                        initiatePurchase()
                    } else {
                        showMessage("Error" + billingResult.debugMessage)
                    }
                }

                override fun onBillingServiceDisconnected() {}
            })
        }
    }

    private fun initiatePurchase() {
        val skuList: MutableList<String> = ArrayList()
        skuList.add(mContext.resources.getString(R.string.product_id))
        val params = SkuDetailsParams.newBuilder()
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
        billingClient.querySkuDetailsAsync(
            params.build()
        ) { billingResult, skuDetailsList ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                if (skuDetailsList != null && skuDetailsList.size > 0) {
                    val flowParams = BillingFlowParams.newBuilder()
                        .setSkuDetails(skuDetailsList[0])
                        .build()
                    billingClient.launchBillingFlow(mContext as Activity, flowParams)
                } else {
                    //try to add item/product id "purchase" inside managed product in google play console
                    showMessage("Purchase Item not Found")
                }
            } else {
                showMessage(" Error " + billingResult.debugMessage)
            }
        }
    }

    /**
     * Verifies that the purchase was signed correctly for this developer's public key.
     *
     * Note: It's strongly recommended to perform such check on your backend since hackers can
     * replace this method with "constant true" if they decompile/rebuild your app.
     *
     */
    private fun verifyValidSignature(signedData: String, signature: String): Boolean {
        return try {
            // To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
            val base64Key = mContext.getString(R.string.license_key)
            Security.verifyPurchase(base64Key, signedData, signature)
        } catch (e: IOException) {
            false
        }
    }

    fun handlePurchases(purchases: List<Purchase>) {
        for (purchase in purchases) {
            //if item is purchased
            if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                if (!verifyValidSignature(purchase.originalJson, purchase.signature)) {
                    // Invalid purchase
                    // show error to user
                   showMessage("Invalid Purchase")
                    return
                }
//                 else purchase is valid
//                if item is purchased and not acknowledged
                if (!purchase.isAcknowledged) {
                    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.purchaseToken)
                        .build()
                    billingClient.acknowledgePurchase(acknowledgePurchaseParams, ackPurchase)
                } else {
                    // Grant entitlement to the user on item purchase
                    // restart activity
                    if (!SharedPreferencesUtils.getPurchasedBillingValue(mContext)) {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext,true)
                        showMessage("Item Purchased")
                        (mContext as Activity).recreate()
                    }
                }
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PENDING) {
                showMessage("Purchase is Pending. Please complete Transaction")
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {
                SharedPreferencesUtils.setPurchasedBillingValue(mContext,false)
                showMessage("Purchase Status Unknown")
            }
        }


    }


    private fun showMessage(message: String) {
        (mContext as Activity).runOnUiThread{
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
        }

    }

    override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
        //if item newly purchased
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            handlePurchases(purchases)
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            val queryAlreadyPurchasesResult =
                billingClient.queryPurchases(BillingClient.SkuType.INAPP)
            val alreadyPurchases = queryAlreadyPurchasesResult.purchasesList
            alreadyPurchases?.let { handlePurchases(it) }
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            showMessage("Purchase Canceled")
        } else {
            showMessage("Error updated" + billingResult.debugMessage)
        }
    }


    fun onDestroyBilling() {
        billingClient.endConnection()
    }

}
import android.text.TextUtils
import android.util.Base64
import java.io.IOException
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec

/**
 * Security-related methods. For a secure implementation, all of this code should be implemented on
 * a server that communicates with the application on the device.
 */
object Security {

    private const val KEY_FACTORY_ALGORITHM = "RSA"
    private const val SIGNATURE_ALGORITHM = "SHA1withRSA"

    /**
     * Verifies that the data was signed with the given signature, and returns the verified
     * purchase.
     * @param base64PublicKey the base64-encoded public key to use for verifying.
     * @param signedData the signed JSON string (signed, not encrypted)
     * @param signature the signature for the data, signed with the private key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun verifyPurchase(
        base64PublicKey: String, signedData: String,
        signature: String
    ): Boolean {
        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
            || TextUtils.isEmpty(signature)
        ) {
            //Purchase verification failed: missing data
            return false
        }
        val key = generatePublicKey(base64PublicKey)
        return verify(key, signedData, signature)
    }

    /**
     * Generates a PublicKey instance from a string containing the Base64-encoded public key.
     *
     * @param encodedPublicKey Base64-encoded public key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun generatePublicKey(encodedPublicKey: String): PublicKey {
        return try {
            val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
            val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
            keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available.
            throw RuntimeException(e)
        } catch (e: InvalidKeySpecException) {
            val msg = "Invalid key specification: $e"
            throw IOException(msg)
        }
    }

    /**
     * Verifies that the signature from the server matches the computed signature on the data.
     * Returns true if the data is correctly signed.
     *
     * @param publicKey public key associated with the developer account
     * @param signedData signed data from server
     * @param signature server signature
     * @return true if the data and signature match
     */
    private fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean {
        val signatureBytes: ByteArray = try {
            Base64.decode(signature, Base64.DEFAULT)
        } catch (e: IllegalArgumentException) {
            //Base64 decoding failed
            return false
        }
        try {
            val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM)
            signatureAlgorithm.initVerify(publicKey)
            signatureAlgorithm.update(signedData.toByteArray())
            return signatureAlgorithm.verify(signatureBytes)
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available
            throw RuntimeException(e)
        } catch (e: InvalidKeyException) {
            //Invalid key specification
        } catch (e: SignatureException) {
            //Signature exception
        }
        return false
    }

}
var inAppPurchase: InAppPurchase  = InAppPurchase(this)
btnInApp.setOnClickListener {
            inAppPurchase.productPurchase()
        }
-----------------------
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.android.billingclient:billing:4.0.0'
<string name="license_key">Place Your ID Here</string>
<string name="product_id">android.test.purchased</string>
import android.app.Activity
import android.content.Context
import android.widget.Toast
import com.android.billingclient.api.*
import java.io.IOException
import java.util.ArrayList

class InAppPurchase(context: Context) : PurchasesUpdatedListener{
    private val mContext: Context = context
    private var billingClient: BillingClient
    var ackPurchase =
        AcknowledgePurchaseResponseListener { billingResult ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                //if purchase is acknowledged
                // Grant entitlement to the user. and restart activity
                SharedPreferencesUtils.setPurchasedBillingValue(mContext, true)
                showMessage("Item Purchased")
                (mContext as Activity).recreate()
            }
        }

    init {
        billingClient =
            BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    val queryPurchase = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                    val queryPurchases = queryPurchase.purchasesList
                    if (queryPurchases != null && queryPurchases.size > 0) {
                        handlePurchases(queryPurchases)
                    } else {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext, false)
                    }
                }
            }

            override fun onBillingServiceDisconnected() {}
        })
    }

    fun productPurchase() {
        //check if service is already connected
        if (billingClient.isReady) {
            initiatePurchase()
        } else {
            billingClient =
                BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
            billingClient.startConnection(object : BillingClientStateListener {
                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                        initiatePurchase()
                    } else {
                        showMessage("Error" + billingResult.debugMessage)
                    }
                }

                override fun onBillingServiceDisconnected() {}
            })
        }
    }

    private fun initiatePurchase() {
        val skuList: MutableList<String> = ArrayList()
        skuList.add(mContext.resources.getString(R.string.product_id))
        val params = SkuDetailsParams.newBuilder()
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
        billingClient.querySkuDetailsAsync(
            params.build()
        ) { billingResult, skuDetailsList ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                if (skuDetailsList != null && skuDetailsList.size > 0) {
                    val flowParams = BillingFlowParams.newBuilder()
                        .setSkuDetails(skuDetailsList[0])
                        .build()
                    billingClient.launchBillingFlow(mContext as Activity, flowParams)
                } else {
                    //try to add item/product id "purchase" inside managed product in google play console
                    showMessage("Purchase Item not Found")
                }
            } else {
                showMessage(" Error " + billingResult.debugMessage)
            }
        }
    }

    /**
     * Verifies that the purchase was signed correctly for this developer's public key.
     *
     * Note: It's strongly recommended to perform such check on your backend since hackers can
     * replace this method with "constant true" if they decompile/rebuild your app.
     *
     */
    private fun verifyValidSignature(signedData: String, signature: String): Boolean {
        return try {
            // To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
            val base64Key = mContext.getString(R.string.license_key)
            Security.verifyPurchase(base64Key, signedData, signature)
        } catch (e: IOException) {
            false
        }
    }

    fun handlePurchases(purchases: List<Purchase>) {
        for (purchase in purchases) {
            //if item is purchased
            if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                if (!verifyValidSignature(purchase.originalJson, purchase.signature)) {
                    // Invalid purchase
                    // show error to user
                   showMessage("Invalid Purchase")
                    return
                }
//                 else purchase is valid
//                if item is purchased and not acknowledged
                if (!purchase.isAcknowledged) {
                    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.purchaseToken)
                        .build()
                    billingClient.acknowledgePurchase(acknowledgePurchaseParams, ackPurchase)
                } else {
                    // Grant entitlement to the user on item purchase
                    // restart activity
                    if (!SharedPreferencesUtils.getPurchasedBillingValue(mContext)) {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext,true)
                        showMessage("Item Purchased")
                        (mContext as Activity).recreate()
                    }
                }
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PENDING) {
                showMessage("Purchase is Pending. Please complete Transaction")
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {
                SharedPreferencesUtils.setPurchasedBillingValue(mContext,false)
                showMessage("Purchase Status Unknown")
            }
        }


    }


    private fun showMessage(message: String) {
        (mContext as Activity).runOnUiThread{
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
        }

    }

    override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
        //if item newly purchased
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            handlePurchases(purchases)
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            val queryAlreadyPurchasesResult =
                billingClient.queryPurchases(BillingClient.SkuType.INAPP)
            val alreadyPurchases = queryAlreadyPurchasesResult.purchasesList
            alreadyPurchases?.let { handlePurchases(it) }
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            showMessage("Purchase Canceled")
        } else {
            showMessage("Error updated" + billingResult.debugMessage)
        }
    }


    fun onDestroyBilling() {
        billingClient.endConnection()
    }

}
import android.text.TextUtils
import android.util.Base64
import java.io.IOException
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec

/**
 * Security-related methods. For a secure implementation, all of this code should be implemented on
 * a server that communicates with the application on the device.
 */
object Security {

    private const val KEY_FACTORY_ALGORITHM = "RSA"
    private const val SIGNATURE_ALGORITHM = "SHA1withRSA"

    /**
     * Verifies that the data was signed with the given signature, and returns the verified
     * purchase.
     * @param base64PublicKey the base64-encoded public key to use for verifying.
     * @param signedData the signed JSON string (signed, not encrypted)
     * @param signature the signature for the data, signed with the private key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun verifyPurchase(
        base64PublicKey: String, signedData: String,
        signature: String
    ): Boolean {
        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
            || TextUtils.isEmpty(signature)
        ) {
            //Purchase verification failed: missing data
            return false
        }
        val key = generatePublicKey(base64PublicKey)
        return verify(key, signedData, signature)
    }

    /**
     * Generates a PublicKey instance from a string containing the Base64-encoded public key.
     *
     * @param encodedPublicKey Base64-encoded public key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun generatePublicKey(encodedPublicKey: String): PublicKey {
        return try {
            val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
            val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
            keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available.
            throw RuntimeException(e)
        } catch (e: InvalidKeySpecException) {
            val msg = "Invalid key specification: $e"
            throw IOException(msg)
        }
    }

    /**
     * Verifies that the signature from the server matches the computed signature on the data.
     * Returns true if the data is correctly signed.
     *
     * @param publicKey public key associated with the developer account
     * @param signedData signed data from server
     * @param signature server signature
     * @return true if the data and signature match
     */
    private fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean {
        val signatureBytes: ByteArray = try {
            Base64.decode(signature, Base64.DEFAULT)
        } catch (e: IllegalArgumentException) {
            //Base64 decoding failed
            return false
        }
        try {
            val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM)
            signatureAlgorithm.initVerify(publicKey)
            signatureAlgorithm.update(signedData.toByteArray())
            return signatureAlgorithm.verify(signatureBytes)
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available
            throw RuntimeException(e)
        } catch (e: InvalidKeyException) {
            //Invalid key specification
        } catch (e: SignatureException) {
            //Signature exception
        }
        return false
    }

}
var inAppPurchase: InAppPurchase  = InAppPurchase(this)
btnInApp.setOnClickListener {
            inAppPurchase.productPurchase()
        }
-----------------------
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.android.billingclient:billing:4.0.0'
<string name="license_key">Place Your ID Here</string>
<string name="product_id">android.test.purchased</string>
import android.app.Activity
import android.content.Context
import android.widget.Toast
import com.android.billingclient.api.*
import java.io.IOException
import java.util.ArrayList

class InAppPurchase(context: Context) : PurchasesUpdatedListener{
    private val mContext: Context = context
    private var billingClient: BillingClient
    var ackPurchase =
        AcknowledgePurchaseResponseListener { billingResult ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                //if purchase is acknowledged
                // Grant entitlement to the user. and restart activity
                SharedPreferencesUtils.setPurchasedBillingValue(mContext, true)
                showMessage("Item Purchased")
                (mContext as Activity).recreate()
            }
        }

    init {
        billingClient =
            BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    val queryPurchase = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                    val queryPurchases = queryPurchase.purchasesList
                    if (queryPurchases != null && queryPurchases.size > 0) {
                        handlePurchases(queryPurchases)
                    } else {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext, false)
                    }
                }
            }

            override fun onBillingServiceDisconnected() {}
        })
    }

    fun productPurchase() {
        //check if service is already connected
        if (billingClient.isReady) {
            initiatePurchase()
        } else {
            billingClient =
                BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
            billingClient.startConnection(object : BillingClientStateListener {
                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                        initiatePurchase()
                    } else {
                        showMessage("Error" + billingResult.debugMessage)
                    }
                }

                override fun onBillingServiceDisconnected() {}
            })
        }
    }

    private fun initiatePurchase() {
        val skuList: MutableList<String> = ArrayList()
        skuList.add(mContext.resources.getString(R.string.product_id))
        val params = SkuDetailsParams.newBuilder()
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
        billingClient.querySkuDetailsAsync(
            params.build()
        ) { billingResult, skuDetailsList ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                if (skuDetailsList != null && skuDetailsList.size > 0) {
                    val flowParams = BillingFlowParams.newBuilder()
                        .setSkuDetails(skuDetailsList[0])
                        .build()
                    billingClient.launchBillingFlow(mContext as Activity, flowParams)
                } else {
                    //try to add item/product id "purchase" inside managed product in google play console
                    showMessage("Purchase Item not Found")
                }
            } else {
                showMessage(" Error " + billingResult.debugMessage)
            }
        }
    }

    /**
     * Verifies that the purchase was signed correctly for this developer's public key.
     *
     * Note: It's strongly recommended to perform such check on your backend since hackers can
     * replace this method with "constant true" if they decompile/rebuild your app.
     *
     */
    private fun verifyValidSignature(signedData: String, signature: String): Boolean {
        return try {
            // To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
            val base64Key = mContext.getString(R.string.license_key)
            Security.verifyPurchase(base64Key, signedData, signature)
        } catch (e: IOException) {
            false
        }
    }

    fun handlePurchases(purchases: List<Purchase>) {
        for (purchase in purchases) {
            //if item is purchased
            if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                if (!verifyValidSignature(purchase.originalJson, purchase.signature)) {
                    // Invalid purchase
                    // show error to user
                   showMessage("Invalid Purchase")
                    return
                }
//                 else purchase is valid
//                if item is purchased and not acknowledged
                if (!purchase.isAcknowledged) {
                    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.purchaseToken)
                        .build()
                    billingClient.acknowledgePurchase(acknowledgePurchaseParams, ackPurchase)
                } else {
                    // Grant entitlement to the user on item purchase
                    // restart activity
                    if (!SharedPreferencesUtils.getPurchasedBillingValue(mContext)) {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext,true)
                        showMessage("Item Purchased")
                        (mContext as Activity).recreate()
                    }
                }
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PENDING) {
                showMessage("Purchase is Pending. Please complete Transaction")
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {
                SharedPreferencesUtils.setPurchasedBillingValue(mContext,false)
                showMessage("Purchase Status Unknown")
            }
        }


    }


    private fun showMessage(message: String) {
        (mContext as Activity).runOnUiThread{
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
        }

    }

    override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
        //if item newly purchased
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            handlePurchases(purchases)
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            val queryAlreadyPurchasesResult =
                billingClient.queryPurchases(BillingClient.SkuType.INAPP)
            val alreadyPurchases = queryAlreadyPurchasesResult.purchasesList
            alreadyPurchases?.let { handlePurchases(it) }
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            showMessage("Purchase Canceled")
        } else {
            showMessage("Error updated" + billingResult.debugMessage)
        }
    }


    fun onDestroyBilling() {
        billingClient.endConnection()
    }

}
import android.text.TextUtils
import android.util.Base64
import java.io.IOException
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec

/**
 * Security-related methods. For a secure implementation, all of this code should be implemented on
 * a server that communicates with the application on the device.
 */
object Security {

    private const val KEY_FACTORY_ALGORITHM = "RSA"
    private const val SIGNATURE_ALGORITHM = "SHA1withRSA"

    /**
     * Verifies that the data was signed with the given signature, and returns the verified
     * purchase.
     * @param base64PublicKey the base64-encoded public key to use for verifying.
     * @param signedData the signed JSON string (signed, not encrypted)
     * @param signature the signature for the data, signed with the private key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun verifyPurchase(
        base64PublicKey: String, signedData: String,
        signature: String
    ): Boolean {
        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
            || TextUtils.isEmpty(signature)
        ) {
            //Purchase verification failed: missing data
            return false
        }
        val key = generatePublicKey(base64PublicKey)
        return verify(key, signedData, signature)
    }

    /**
     * Generates a PublicKey instance from a string containing the Base64-encoded public key.
     *
     * @param encodedPublicKey Base64-encoded public key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun generatePublicKey(encodedPublicKey: String): PublicKey {
        return try {
            val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
            val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
            keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available.
            throw RuntimeException(e)
        } catch (e: InvalidKeySpecException) {
            val msg = "Invalid key specification: $e"
            throw IOException(msg)
        }
    }

    /**
     * Verifies that the signature from the server matches the computed signature on the data.
     * Returns true if the data is correctly signed.
     *
     * @param publicKey public key associated with the developer account
     * @param signedData signed data from server
     * @param signature server signature
     * @return true if the data and signature match
     */
    private fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean {
        val signatureBytes: ByteArray = try {
            Base64.decode(signature, Base64.DEFAULT)
        } catch (e: IllegalArgumentException) {
            //Base64 decoding failed
            return false
        }
        try {
            val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM)
            signatureAlgorithm.initVerify(publicKey)
            signatureAlgorithm.update(signedData.toByteArray())
            return signatureAlgorithm.verify(signatureBytes)
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available
            throw RuntimeException(e)
        } catch (e: InvalidKeyException) {
            //Invalid key specification
        } catch (e: SignatureException) {
            //Signature exception
        }
        return false
    }

}
var inAppPurchase: InAppPurchase  = InAppPurchase(this)
btnInApp.setOnClickListener {
            inAppPurchase.productPurchase()
        }
-----------------------
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.android.billingclient:billing:4.0.0'
<string name="license_key">Place Your ID Here</string>
<string name="product_id">android.test.purchased</string>
import android.app.Activity
import android.content.Context
import android.widget.Toast
import com.android.billingclient.api.*
import java.io.IOException
import java.util.ArrayList

class InAppPurchase(context: Context) : PurchasesUpdatedListener{
    private val mContext: Context = context
    private var billingClient: BillingClient
    var ackPurchase =
        AcknowledgePurchaseResponseListener { billingResult ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                //if purchase is acknowledged
                // Grant entitlement to the user. and restart activity
                SharedPreferencesUtils.setPurchasedBillingValue(mContext, true)
                showMessage("Item Purchased")
                (mContext as Activity).recreate()
            }
        }

    init {
        billingClient =
            BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    val queryPurchase = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                    val queryPurchases = queryPurchase.purchasesList
                    if (queryPurchases != null && queryPurchases.size > 0) {
                        handlePurchases(queryPurchases)
                    } else {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext, false)
                    }
                }
            }

            override fun onBillingServiceDisconnected() {}
        })
    }

    fun productPurchase() {
        //check if service is already connected
        if (billingClient.isReady) {
            initiatePurchase()
        } else {
            billingClient =
                BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
            billingClient.startConnection(object : BillingClientStateListener {
                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                        initiatePurchase()
                    } else {
                        showMessage("Error" + billingResult.debugMessage)
                    }
                }

                override fun onBillingServiceDisconnected() {}
            })
        }
    }

    private fun initiatePurchase() {
        val skuList: MutableList<String> = ArrayList()
        skuList.add(mContext.resources.getString(R.string.product_id))
        val params = SkuDetailsParams.newBuilder()
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
        billingClient.querySkuDetailsAsync(
            params.build()
        ) { billingResult, skuDetailsList ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                if (skuDetailsList != null && skuDetailsList.size > 0) {
                    val flowParams = BillingFlowParams.newBuilder()
                        .setSkuDetails(skuDetailsList[0])
                        .build()
                    billingClient.launchBillingFlow(mContext as Activity, flowParams)
                } else {
                    //try to add item/product id "purchase" inside managed product in google play console
                    showMessage("Purchase Item not Found")
                }
            } else {
                showMessage(" Error " + billingResult.debugMessage)
            }
        }
    }

    /**
     * Verifies that the purchase was signed correctly for this developer's public key.
     *
     * Note: It's strongly recommended to perform such check on your backend since hackers can
     * replace this method with "constant true" if they decompile/rebuild your app.
     *
     */
    private fun verifyValidSignature(signedData: String, signature: String): Boolean {
        return try {
            // To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
            val base64Key = mContext.getString(R.string.license_key)
            Security.verifyPurchase(base64Key, signedData, signature)
        } catch (e: IOException) {
            false
        }
    }

    fun handlePurchases(purchases: List<Purchase>) {
        for (purchase in purchases) {
            //if item is purchased
            if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                if (!verifyValidSignature(purchase.originalJson, purchase.signature)) {
                    // Invalid purchase
                    // show error to user
                   showMessage("Invalid Purchase")
                    return
                }
//                 else purchase is valid
//                if item is purchased and not acknowledged
                if (!purchase.isAcknowledged) {
                    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.purchaseToken)
                        .build()
                    billingClient.acknowledgePurchase(acknowledgePurchaseParams, ackPurchase)
                } else {
                    // Grant entitlement to the user on item purchase
                    // restart activity
                    if (!SharedPreferencesUtils.getPurchasedBillingValue(mContext)) {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext,true)
                        showMessage("Item Purchased")
                        (mContext as Activity).recreate()
                    }
                }
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PENDING) {
                showMessage("Purchase is Pending. Please complete Transaction")
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {
                SharedPreferencesUtils.setPurchasedBillingValue(mContext,false)
                showMessage("Purchase Status Unknown")
            }
        }


    }


    private fun showMessage(message: String) {
        (mContext as Activity).runOnUiThread{
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
        }

    }

    override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
        //if item newly purchased
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            handlePurchases(purchases)
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            val queryAlreadyPurchasesResult =
                billingClient.queryPurchases(BillingClient.SkuType.INAPP)
            val alreadyPurchases = queryAlreadyPurchasesResult.purchasesList
            alreadyPurchases?.let { handlePurchases(it) }
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            showMessage("Purchase Canceled")
        } else {
            showMessage("Error updated" + billingResult.debugMessage)
        }
    }


    fun onDestroyBilling() {
        billingClient.endConnection()
    }

}
import android.text.TextUtils
import android.util.Base64
import java.io.IOException
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec

/**
 * Security-related methods. For a secure implementation, all of this code should be implemented on
 * a server that communicates with the application on the device.
 */
object Security {

    private const val KEY_FACTORY_ALGORITHM = "RSA"
    private const val SIGNATURE_ALGORITHM = "SHA1withRSA"

    /**
     * Verifies that the data was signed with the given signature, and returns the verified
     * purchase.
     * @param base64PublicKey the base64-encoded public key to use for verifying.
     * @param signedData the signed JSON string (signed, not encrypted)
     * @param signature the signature for the data, signed with the private key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun verifyPurchase(
        base64PublicKey: String, signedData: String,
        signature: String
    ): Boolean {
        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
            || TextUtils.isEmpty(signature)
        ) {
            //Purchase verification failed: missing data
            return false
        }
        val key = generatePublicKey(base64PublicKey)
        return verify(key, signedData, signature)
    }

    /**
     * Generates a PublicKey instance from a string containing the Base64-encoded public key.
     *
     * @param encodedPublicKey Base64-encoded public key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun generatePublicKey(encodedPublicKey: String): PublicKey {
        return try {
            val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
            val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
            keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available.
            throw RuntimeException(e)
        } catch (e: InvalidKeySpecException) {
            val msg = "Invalid key specification: $e"
            throw IOException(msg)
        }
    }

    /**
     * Verifies that the signature from the server matches the computed signature on the data.
     * Returns true if the data is correctly signed.
     *
     * @param publicKey public key associated with the developer account
     * @param signedData signed data from server
     * @param signature server signature
     * @return true if the data and signature match
     */
    private fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean {
        val signatureBytes: ByteArray = try {
            Base64.decode(signature, Base64.DEFAULT)
        } catch (e: IllegalArgumentException) {
            //Base64 decoding failed
            return false
        }
        try {
            val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM)
            signatureAlgorithm.initVerify(publicKey)
            signatureAlgorithm.update(signedData.toByteArray())
            return signatureAlgorithm.verify(signatureBytes)
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available
            throw RuntimeException(e)
        } catch (e: InvalidKeyException) {
            //Invalid key specification
        } catch (e: SignatureException) {
            //Signature exception
        }
        return false
    }

}
var inAppPurchase: InAppPurchase  = InAppPurchase(this)
btnInApp.setOnClickListener {
            inAppPurchase.productPurchase()
        }
-----------------------
implementation 'com.anjlab.android.iab.v3:library:1.1.0'
implementation 'com.android.billingclient:billing:4.0.0'
<string name="license_key">Place Your ID Here</string>
<string name="product_id">android.test.purchased</string>
import android.app.Activity
import android.content.Context
import android.widget.Toast
import com.android.billingclient.api.*
import java.io.IOException
import java.util.ArrayList

class InAppPurchase(context: Context) : PurchasesUpdatedListener{
    private val mContext: Context = context
    private var billingClient: BillingClient
    var ackPurchase =
        AcknowledgePurchaseResponseListener { billingResult ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                //if purchase is acknowledged
                // Grant entitlement to the user. and restart activity
                SharedPreferencesUtils.setPurchasedBillingValue(mContext, true)
                showMessage("Item Purchased")
                (mContext as Activity).recreate()
            }
        }

    init {
        billingClient =
            BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                    val queryPurchase = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                    val queryPurchases = queryPurchase.purchasesList
                    if (queryPurchases != null && queryPurchases.size > 0) {
                        handlePurchases(queryPurchases)
                    } else {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext, false)
                    }
                }
            }

            override fun onBillingServiceDisconnected() {}
        })
    }

    fun productPurchase() {
        //check if service is already connected
        if (billingClient.isReady) {
            initiatePurchase()
        } else {
            billingClient =
                BillingClient.newBuilder(mContext).enablePendingPurchases().setListener(this).build()
            billingClient.startConnection(object : BillingClientStateListener {
                override fun onBillingSetupFinished(billingResult: BillingResult) {
                    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                        initiatePurchase()
                    } else {
                        showMessage("Error" + billingResult.debugMessage)
                    }
                }

                override fun onBillingServiceDisconnected() {}
            })
        }
    }

    private fun initiatePurchase() {
        val skuList: MutableList<String> = ArrayList()
        skuList.add(mContext.resources.getString(R.string.product_id))
        val params = SkuDetailsParams.newBuilder()
        params.setSkusList(skuList).setType(BillingClient.SkuType.INAPP)
        billingClient.querySkuDetailsAsync(
            params.build()
        ) { billingResult, skuDetailsList ->
            if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
                if (skuDetailsList != null && skuDetailsList.size > 0) {
                    val flowParams = BillingFlowParams.newBuilder()
                        .setSkuDetails(skuDetailsList[0])
                        .build()
                    billingClient.launchBillingFlow(mContext as Activity, flowParams)
                } else {
                    //try to add item/product id "purchase" inside managed product in google play console
                    showMessage("Purchase Item not Found")
                }
            } else {
                showMessage(" Error " + billingResult.debugMessage)
            }
        }
    }

    /**
     * Verifies that the purchase was signed correctly for this developer's public key.
     *
     * Note: It's strongly recommended to perform such check on your backend since hackers can
     * replace this method with "constant true" if they decompile/rebuild your app.
     *
     */
    private fun verifyValidSignature(signedData: String, signature: String): Boolean {
        return try {
            // To get key go to Developer Console > Select your app > Development Tools > Services & APIs.
            val base64Key = mContext.getString(R.string.license_key)
            Security.verifyPurchase(base64Key, signedData, signature)
        } catch (e: IOException) {
            false
        }
    }

    fun handlePurchases(purchases: List<Purchase>) {
        for (purchase in purchases) {
            //if item is purchased
            if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                if (!verifyValidSignature(purchase.originalJson, purchase.signature)) {
                    // Invalid purchase
                    // show error to user
                   showMessage("Invalid Purchase")
                    return
                }
//                 else purchase is valid
//                if item is purchased and not acknowledged
                if (!purchase.isAcknowledged) {
                    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
                        .setPurchaseToken(purchase.purchaseToken)
                        .build()
                    billingClient.acknowledgePurchase(acknowledgePurchaseParams, ackPurchase)
                } else {
                    // Grant entitlement to the user on item purchase
                    // restart activity
                    if (!SharedPreferencesUtils.getPurchasedBillingValue(mContext)) {
                        SharedPreferencesUtils.setPurchasedBillingValue(mContext,true)
                        showMessage("Item Purchased")
                        (mContext as Activity).recreate()
                    }
                }
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.PENDING) {
                showMessage("Purchase is Pending. Please complete Transaction")
            } else if (mContext.getString(R.string.product_id) == purchase.skus[0] && purchase.purchaseState == Purchase.PurchaseState.UNSPECIFIED_STATE) {
                SharedPreferencesUtils.setPurchasedBillingValue(mContext,false)
                showMessage("Purchase Status Unknown")
            }
        }


    }


    private fun showMessage(message: String) {
        (mContext as Activity).runOnUiThread{
            Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
        }

    }

    override fun onPurchasesUpdated(billingResult: BillingResult, purchases: MutableList<Purchase>?) {
        //if item newly purchased
        if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
            handlePurchases(purchases)
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            val queryAlreadyPurchasesResult =
                billingClient.queryPurchases(BillingClient.SkuType.INAPP)
            val alreadyPurchases = queryAlreadyPurchasesResult.purchasesList
            alreadyPurchases?.let { handlePurchases(it) }
        } else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            showMessage("Purchase Canceled")
        } else {
            showMessage("Error updated" + billingResult.debugMessage)
        }
    }


    fun onDestroyBilling() {
        billingClient.endConnection()
    }

}
import android.text.TextUtils
import android.util.Base64
import java.io.IOException
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.X509EncodedKeySpec

/**
 * Security-related methods. For a secure implementation, all of this code should be implemented on
 * a server that communicates with the application on the device.
 */
object Security {

    private const val KEY_FACTORY_ALGORITHM = "RSA"
    private const val SIGNATURE_ALGORITHM = "SHA1withRSA"

    /**
     * Verifies that the data was signed with the given signature, and returns the verified
     * purchase.
     * @param base64PublicKey the base64-encoded public key to use for verifying.
     * @param signedData the signed JSON string (signed, not encrypted)
     * @param signature the signature for the data, signed with the private key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun verifyPurchase(
        base64PublicKey: String, signedData: String,
        signature: String
    ): Boolean {
        if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey)
            || TextUtils.isEmpty(signature)
        ) {
            //Purchase verification failed: missing data
            return false
        }
        val key = generatePublicKey(base64PublicKey)
        return verify(key, signedData, signature)
    }

    /**
     * Generates a PublicKey instance from a string containing the Base64-encoded public key.
     *
     * @param encodedPublicKey Base64-encoded public key
     * @throws IOException if encoding algorithm is not supported or key specification
     * is invalid
     */
    @Throws(IOException::class)
    fun generatePublicKey(encodedPublicKey: String): PublicKey {
        return try {
            val decodedKey = Base64.decode(encodedPublicKey, Base64.DEFAULT)
            val keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM)
            keyFactory.generatePublic(X509EncodedKeySpec(decodedKey))
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available.
            throw RuntimeException(e)
        } catch (e: InvalidKeySpecException) {
            val msg = "Invalid key specification: $e"
            throw IOException(msg)
        }
    }

    /**
     * Verifies that the signature from the server matches the computed signature on the data.
     * Returns true if the data is correctly signed.
     *
     * @param publicKey public key associated with the developer account
     * @param signedData signed data from server
     * @param signature server signature
     * @return true if the data and signature match
     */
    private fun verify(publicKey: PublicKey, signedData: String, signature: String): Boolean {
        val signatureBytes: ByteArray = try {
            Base64.decode(signature, Base64.DEFAULT)
        } catch (e: IllegalArgumentException) {
            //Base64 decoding failed
            return false
        }
        try {
            val signatureAlgorithm = Signature.getInstance(SIGNATURE_ALGORITHM)
            signatureAlgorithm.initVerify(publicKey)
            signatureAlgorithm.update(signedData.toByteArray())
            return signatureAlgorithm.verify(signatureBytes)
        } catch (e: NoSuchAlgorithmException) {
            // "RSA" is guaranteed to be available
            throw RuntimeException(e)
        } catch (e: InvalidKeyException) {
            //Invalid key specification
        } catch (e: SignatureException) {
            //Signature exception
        }
        return false
    }

}
var inAppPurchase: InAppPurchase  = InAppPurchase(this)
btnInApp.setOnClickListener {
            inAppPurchase.productPurchase()
        }

Community Discussions

Trending Discussions on gson
  • Retrofit OkHttp - &quot;unexpected end of stream&quot;
  • A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptWithoutKotlincTask$KaptExecutionWorkAction?java.lang.reflect.Invocation?
  • OkHttpClient sometimes getting incomplete json response
  • Caused by java.lang.RuntimeException: Missing type parameter
  • android:exported needs to be explicitly specified for &lt;activity&gt;. Apps targeting Android 12 and higher are required to specify
  • Android Build Error: &quot;lStar not found...&quot;
  • Could not resolve com.google.guava:guava:30.1-jre - Gradle project sync failed. Basic functionality will not work properly - in kotlin project
  • Retrofit2, how I can convert response with diferent object names but same data types
  • using webclient to call the grapql mutation API in spring boot
  • Android API 31 FLAG_IMMUTABLE Error using Firebase Auth UI
Trending Discussions on gson

QUESTION

Retrofit OkHttp - &quot;unexpected end of stream&quot;

Asked 2022-Mar-27 at 18:38

I am getting "Unexpected end of stream" while using Retrofit (2.9.0) with OkHttp3 (4.9.1)

Retrofit configuration:

interface ApiServiceInterface {

    companion object Factory{

        fun create(): ApiServiceInterface {
            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY

            val client = OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30,TimeUnit.SECONDS)
                .addInterceptor(Interceptor { chain ->
                    chain.request().newBuilder()
                        .addHeader("Connection", "close")
                        .addHeader("Accept-Encoding", "identity")
                        .build()
                        .let(chain::proceed)
                })
                .retryOnConnectionFailure(true)
                .connectionPool(ConnectionPool(0, 5, TimeUnit.MINUTES))
                .protocols(listOf(Protocol.HTTP_1_1))
                .build()
            val gson = GsonBuilder().setLenient().create()
            val retrofit = Retrofit.Builder()
                    .addCallAdapterFactory(CoroutineCallAdapterFactory())
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .baseUrl("http://***.***.***.***:****")
                    .client(client)
                    .build()
            return retrofit.create(ApiServiceInterface::class.java)
        }
    }


    @Headers("Content-type: application/json", "Connection: close", "Accept-Encoding: identity")
    @POST("/")
    fun requestAsync(@Body data: JsonObject): Deferred<Response>
}

So far I have found out the following:

  1. This issue only occurs for me while using Android Studio emulators running from Windows series OS (7, 10, 11) - this was reproduced on 2 different laptops from different networks.
  2. If running Android Studio emulators under OS X the issue won't reproduce in 100% cases.
  3. ARC/Postman clients never has any issues completing same requests to my backend.
  4. On running from Windows Android Studio emulators this issue reproduces in about 10-50% requests, other requests work without problem.
  5. The identical requests can result in this error or complete sucessfully.
  6. Responses which take about 11 sec to complete can result in success, while responses which take about 100 msec to complete can result in this error.
  7. Commenting off .client(client) from retrofit configuration eliminates this issue, but I loose the opportunity to use interceptors and other OkHttp functionality.
  8. Adding headers (Connection: close, Accept-Encoding: identity) does not solve issue.
  9. Turning retryOnConnectionFailure on or off has no impact on issue as well.
  10. Changing HttpLoggingInterceptor level or removing it completely does not solve issue.

Server-side configuration:

const http = require('http');
const server = http.createServer((req, res) => {

    const callback = function(code, request, data) {
        let result = responser(code, request, data);
        res.writeHead(200, {
            'Content-Type' : 'x-application/json',
            'Connection': 'close',
            'Content-Length': Buffer.byteLength(result)
        });
        res.end(result);
    };

    ...
}

server.listen(process.env.PORT, process.env.HOSTNAME, () => {
    console.log(`Server is running`);
});

So, based on 1,2,3 - this is unlikely server-side issue.
Based on 4, 5, 6 - it is not malformed request related or execution time related issue. Guessing from 7 - this issue roots lay in OkHttp rather than Retrofit itself.

I have read almost half of stackoverflow is search of resolution, like:
unexpected end of stream retrofit
Retrofit OkHttp unexpected end of stream on Connection error
and also discussion at OkHttp on Github:
https://github.com/square/okhttp/issues/3682
https://github.com/square/okhttp/issues/3715
But nothing helped so far.

Any idea what might be causing the problem?

Update

I've got more info on situation.

First, I changed headers on backend to not to pass Content-Length and pass Transfer-Encoding : identity instead. I don't know why, but Postman gives an error if theese headers are present both, saying it is not right.

res.writeHead(200, {
            'Content-Type' : 'x-application/json',
            'Connection': 'close',
            'Transfer-Encoding': 'identity'
        });

After that I started to receive another error on Windows hosted Android Studio emulators (with equal ratio of fail / success to "Unexpected end of stream")

2021-12-09 14:58:19.696 401-401/? D/P2P-> FRG DEBUG:: java.io.EOFException: End of input at line 1 column 1807 path $.meta
        at com.google.gson.stream.JsonReader.nextNonWhitespace(JsonReader.java:1397)
        at com.google.gson.stream.JsonReader.doPeek(JsonReader.java:483)
        at com.google.gson.stream.JsonReader.hasNext(JsonReader.java:415)
        at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:216)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:40)
        at retrofit2.converter.gson.GsonResponseBodyConverter.convert(GsonResponseBodyConverter.java:27)
        at retrofit2.OkHttpCall.parseResponse(OkHttpCall.java:243)
        at retrofit2.OkHttpCall$1.onResponse(OkHttpCall.java:153)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:764)

Spending a lot of time debugging this issue I have found that this exception was generated by JsonReader.java in method nextNonWhitespace where it try to to get colons, double quotes and curly or square braces to compose json object from decoded as char array buffer.

This buffer itself is received in fillBuffer method of the same module and it has length limit of 1024 elements. In my case the backend response is longer that this value (1807 chars), so while JsonReader.java parses my response as json object it do this in 2 iterations.

Each iteration it fills the buffer here:

int total;
while ((total = in.read(buffer, limit, buffer.length - limit)) != -1) {
    limit += total;

    // if this is the first read, consume an optional byte order mark (BOM) if it exists
    if (lineNumber == 0 && lineStart == 0 && limit > 0 && buffer[0] == '\ufeff') {
        pos++;
        lineStart++;
        minimum++;
    }

    if (limit >= minimum) {
        return true;
    }
}

the read method is called on ResponseBody.kt class from okhttp3

@Throws(IOException::class)
    override fun read(cbuf: CharArray, off: Int, len: Int): Int {
      if (closed) throw IOException("Stream closed")

      val finalDelegate = delegate ?: InputStreamReader(
          source.inputStream(),
          source.readBomAsCharset(charset)).also {
        delegate = it
      }
      return finalDelegate.read(cbuf, off, len)
    }

The main problem is:

At first iteration all goes well, ResponseBody.kt "reads" first 1024 chars and gives them to JsonReader.java where it composes a part of response object.

1st iteration

When second iteration comes ResponseBody.kt "reads" the last part of response and fills with it the start of char buffer, so char buffer now contains as its first elements the tail of response, and after that - all elements which was left there after firts iteration.

The main problem is that it im most cases (about 80%) looses last char from response, in about 10% in looses 2 last chars from response and in about 10% it reads all chars. Here is shots:

Wrong number of chars It must contains 783 chars to complete json, but as shown at line 1290 it receives only 782.

Looking at buffer itself Wrong buffer
the char at 782 index (783 in order) must be second curly brace that closes json root, but instead of it there are leftovers from first iteration started. This results in exception mentioned above.

Now if we look at situation where requests finished successfully: Good number of chars With the same request it occasionly returns valid number of chars: 783

And the buffer itself is: enter image description here Now the second brace is present where it must be.

In this case request will be successfull.

The same response ending from Postman: postman The Postman success rate in parsing response is 100%, the same is true for OS X hosted android studio emulators and real devices I've used.

Update 2

It seems full buffer obtained in RealBufferedSource.kt:

internal inline fun RealBufferedSource.commonSelect(options: Options): Int {
  check(!closed) { "closed" }

  while (true) {
    val index = buffer.selectPrefix(options, selectTruncated = true)
    when (index) {
      -1 -> {
        return -1
      }
      -2 -> {
        // We need to grow the buffer. Do that, then try it all again.
        if (source.read(buffer, Segment.SIZE.toLong()) == -1L) return -1
      }
      else -> {
        // We matched a full byte string: consume it and return it.
        val selectedSize = options.byteStrings[index].size
        buffer.skip(selectedSize.toLong())
        return index
      }
    }
  }
}

and here it is already missing last char: enter image description here

Update 3

Found this unsolved question which is exactly the same behavior:
Retrofit Json data truncated
Also comment from Android Studio emulators issues tracker:
https://issuetracker.google.com/issues/119027639#comment9

ANSWER

Answered 2022-Mar-27 at 18:38

OK, It took some time, but I've found what was going wrong and how to workaround that.

When Android Studio's emulators running in Windows series OS (checked for 7 & 10) receive json-typed reply from server with retrofit it can with various probability loose 1 or 2 last symbols of the body when it is decoded to string, this symbols contain closing curly brackets and so such body could not be parsed to object by gson converter which results in throwing exception.

The idea of workaround I found is to add an interceptor to retrofit which would check the decoded to string body if its last symbols match those of valid json response and add them if they are missed.

interface ApiServiceInterface {

    companion object Factory{

        fun create(): ApiServiceInterface {
            val interceptor = HttpLoggingInterceptor()
            interceptor.level = HttpLoggingInterceptor.Level.BODY
            
            val stringInterceptor = Interceptor { chain: Interceptor.Chain ->
                val request = chain.request()
                val response = chain.proceed(request)
                val source = response.body()?.source()
                source?.request(Long.MAX_VALUE)
                val buffer = source?.buffer()
                var responseString = buffer?.clone()?.readString(Charset.forName("UTF-8"))
                if (responseString != null && responseString.length > 2) {
                    val lastTwo = responseString.takeLast(2)
                    if (lastTwo != "}}") {
                        val lastOne = responseString.takeLast(1)
                        responseString = if (lastOne != "}") {
                            "$responseString}}"
                        } else {
                            "$responseString}"
                        }
                    }
                }
                val contentType = response.body()?.contentType()
                val body = ResponseBody.create(contentType, responseString ?: "")
                return@Interceptor response.newBuilder().body(body).build()
            }

            val client = OkHttpClient.Builder()
                .connectTimeout(30, TimeUnit.SECONDS)
                .writeTimeout(30, TimeUnit.SECONDS)
                .readTimeout(30,TimeUnit.SECONDS)
                .addInterceptor(interceptor)
                .addInterceptor(stringInterceptor)
                .retryOnConnectionFailure(true)
                .connectionPool(ConnectionPool(0, 5, TimeUnit.MINUTES))
                .protocols(listOf(Protocol.HTTP_1_1))
                .build()
            val gson = GsonBuilder().create()
            val retrofit = Retrofit.Builder()
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addConverterFactory(ScalarsConverterFactory.create())
                .baseUrl("http://3.124.6.203:5000")
                .client(client)
                .build()
            return retrofit.create(ApiServiceInterface::class.java)
        }
    }


    @Headers("Content-type: application/json", "Connection: close", "Accept-Encoding: identity")
    @POST("/")
    fun requestAsync(@Body data: JsonObject): Deferred<Response>
}

After this changes the issue didn't occure.

Source https://stackoverflow.com/questions/70246508

Community Discussions, Code Snippets contain sources that include Stack Exchange Network

Vulnerabilities

No vulnerabilities reported

Install gson

Gson jar downloads are available from Maven Central.

Support

Please use the 'gson' tag on StackOverflow or the google-gson Google group to discuss Gson or to post questions.

DOWNLOAD this Library from

Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
over 430 million Knowledge Items
Find more libraries
Reuse Solution Kits and Libraries Curated by Popular Use Cases

Save this library and start creating your kit

Explore Related Topics

Share this Page

share link
Consider Popular JSON Processing Libraries
Compare JSON Processing Libraries with Highest Support
Compare JSON Processing Libraries with Permissive License
Compare JSON Processing Libraries with Highest Reuse
Find, review, and download reusable Libraries, Code Snippets, Cloud APIs from
over 430 million Knowledge Items
Find more libraries
Reuse Solution Kits and Libraries Curated by Popular Use Cases

Save this library and start creating your kit

  • © 2022 Open Weaver Inc.