diff --git a/lottie/src/main/java/com/airbnb/lottie/L.java b/lottie/src/main/java/com/airbnb/lottie/L.java index f26b6aeac8..834b7e7a7b 100644 --- a/lottie/src/main/java/com/airbnb/lottie/L.java +++ b/lottie/src/main/java/com/airbnb/lottie/L.java @@ -1,5 +1,16 @@ package com.airbnb.lottie; +import android.content.Context; + +import com.airbnb.lottie.network.LottieNetworkCacheProvider; +import com.airbnb.lottie.network.DefaultLottieNetworkFetcher; +import com.airbnb.lottie.network.LottieNetworkFetcher; +import com.airbnb.lottie.network.NetworkCache; +import com.airbnb.lottie.network.NetworkFetcher; + +import java.io.File; + +import androidx.annotation.NonNull; import androidx.annotation.RestrictTo; import androidx.core.os.TraceCompat; @@ -16,6 +27,12 @@ public class L { private static int traceDepth = 0; private static int depthPastMaxDepth = 0; + private static LottieNetworkFetcher fetcher; + private static LottieNetworkCacheProvider cacheProvider; + + private static volatile NetworkFetcher networkFetcher; + private static volatile NetworkCache networkCache; + public static void setTraceEnabled(boolean enabled) { if (traceEnabled == enabled) { return; @@ -60,4 +77,44 @@ public static float endSection(String section) { TraceCompat.endSection(); return (System.nanoTime() - startTimeNs[traceDepth]) / 1000000f; } + + public static void setFetcher(LottieNetworkFetcher customFetcher) { + fetcher = customFetcher; + } + + public static void setCacheProvider(LottieNetworkCacheProvider customProvider) { + cacheProvider = customProvider; + } + + @NonNull + public static NetworkFetcher networkFetcher(@NonNull Context context) { + NetworkFetcher local = networkFetcher; + if (local == null) { + synchronized (NetworkFetcher.class) { + local = networkFetcher; + if (local == null) { + networkFetcher = local = new NetworkFetcher(networkCache(context), fetcher != null ? fetcher : new DefaultLottieNetworkFetcher()); + } + } + } + return local; + } + + @NonNull + public static NetworkCache networkCache(@NonNull final Context context) { + NetworkCache local = networkCache; + if (local == null) { + synchronized (NetworkCache.class) { + local = networkCache; + if (local == null) { + networkCache = local = new NetworkCache(cacheProvider != null ? cacheProvider : new LottieNetworkCacheProvider() { + @Override @NonNull public File getCacheDir() { + return new File(context.getCacheDir(), "lottie_network_cache"); + } + }); + } + } + } + return local; + } } diff --git a/lottie/src/main/java/com/airbnb/lottie/Lottie.java b/lottie/src/main/java/com/airbnb/lottie/Lottie.java new file mode 100644 index 0000000000..96c7a6f255 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/Lottie.java @@ -0,0 +1,17 @@ +package com.airbnb.lottie; + +import androidx.annotation.NonNull; + +/** + * Class for initializing the library with custom config + */ +public class Lottie { + + private Lottie() { + } + + public static void initialize(@NonNull final LottieConfig lottieConfig) { + L.setFetcher(lottieConfig.networkFetcher); + L.setCacheProvider(lottieConfig.cacheProvider); + } +} \ No newline at end of file diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java index 8d6fb95042..f4a1a6ab69 100644 --- a/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java +++ b/lottie/src/main/java/com/airbnb/lottie/LottieCompositionFactory.java @@ -5,15 +5,13 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.os.Build; import com.airbnb.lottie.model.LottieCompositionCache; -import com.airbnb.lottie.network.NetworkCache; -import com.airbnb.lottie.network.NetworkFetcher; import com.airbnb.lottie.parser.LottieCompositionMoshiParser; import com.airbnb.lottie.parser.moshi.JsonReader; import com.airbnb.lottie.utils.Utils; + import org.json.JSONObject; import java.io.ByteArrayInputStream; @@ -65,14 +63,14 @@ public static void setMaxCacheSize(int size) { public static void clearCache(Context context) { taskCache.clear(); LottieCompositionCache.getInstance().clear(); - new NetworkCache(context).clear(); + L.networkCache(context).clear(); } /** * Fetch an animation from an http url. Once it is downloaded once, Lottie will cache the file to disk for * future use. Because of this, you may call `fromUrl` ahead of time to warm the cache if you think you * might need an animation in the future. - * + *

* To skip the cache, add null as a third parameter. */ public static LottieTask fromUrl(final Context context, final String url) { @@ -88,7 +86,7 @@ public static LottieTask fromUrl(final Context context, final return cache(cacheKey, new Callable>() { @Override public LottieResult call() { - return NetworkFetcher.fetchSync(context, url, cacheKey); + return L.networkFetcher(context).fetchSync(url, cacheKey); } }); } @@ -111,14 +109,14 @@ public static LottieResult fromUrlSync(Context context, Strin */ @WorkerThread public static LottieResult fromUrlSync(Context context, String url, @Nullable String cacheKey) { - return NetworkFetcher.fetchSync(context, url, cacheKey); + return L.networkFetcher(context).fetchSync(url, cacheKey); } /** * Parse an animation from src/main/assets. It is recommended to use {@link #fromRawRes(Context, int)} instead. * The asset file name will be used as a cache key so future usages won't have to parse the json again. * However, if your animation has images, you may package the json and images as a single flattened zip file in assets. - * + *

* To skip the cache, add null as a third parameter. * * @see #fromZipStream(ZipInputStream, String) @@ -132,7 +130,7 @@ public static LottieTask fromAsset(Context context, final Str * Parse an animation from src/main/assets. It is recommended to use {@link #fromRawRes(Context, int)} instead. * The asset file name will be used as a cache key so future usages won't have to parse the json again. * However, if your animation has images, you may package the json and images as a single flattened zip file in assets. - * + *

* Pass null as the cache key to skip the cache. * * @see #fromZipStream(ZipInputStream, String) @@ -152,22 +150,22 @@ public LottieResult call() { * Parse an animation from src/main/assets. It is recommended to use {@link #fromRawRes(Context, int)} instead. * The asset file name will be used as a cache key so future usages won't have to parse the json again. * However, if your animation has images, you may package the json and images as a single flattened zip file in assets. - * + *

* To skip the cache, add null as a third parameter. * * @see #fromZipStreamSync(ZipInputStream, String) */ @WorkerThread public static LottieResult fromAssetSync(Context context, String fileName) { - String cacheKey = "asset_" + fileName; - return fromAssetSync(context, fileName, cacheKey); + String cacheKey = "asset_" + fileName; + return fromAssetSync(context, fileName, cacheKey); } /** * Parse an animation from src/main/assets. It is recommended to use {@link #fromRawRes(Context, int)} instead. * The asset file name will be used as a cache key so future usages won't have to parse the json again. * However, if your animation has images, you may package the json and images as a single flattened zip file in assets. - * + *

* Pass null as the cache key to skip the cache. * * @see #fromZipStreamSync(ZipInputStream, String) @@ -191,7 +189,7 @@ public static LottieResult fromAssetSync(Context context, Str * The resource id will be used as a cache key so future usages won't parse the json again. * Note: to correctly load dark mode (-night) resources, make sure you pass Activity as a context (instead of e.g. the application context). * The Activity won't be leaked. - * + *

* To skip the cache, add null as a third parameter. */ public static LottieTask fromRawRes(Context context, @RawRes final int rawRes) { @@ -204,7 +202,7 @@ public static LottieTask fromRawRes(Context context, @RawRes * The resource id will be used as a cache key so future usages won't parse the json again. * Note: to correctly load dark mode (-night) resources, make sure you pass Activity as a context (instead of e.g. the application context). * The Activity won't be leaked. - * + *

* Pass null as the cache key to skip caching. */ public static LottieTask fromRawRes(Context context, @RawRes final int rawRes, @Nullable final String cacheKey) { @@ -227,7 +225,7 @@ public LottieResult call() { * The resource id will be used as a cache key so future usages won't parse the json again. * Note: to correctly load dark mode (-night) resources, make sure you pass Activity as a context (instead of e.g. the application context). * The Activity won't be leaked. - * + *

* To skip the cache, add null as a third parameter. */ @WorkerThread @@ -241,7 +239,7 @@ public static LottieResult fromRawResSync(Context context, @R * The resource id will be used as a cache key so future usages won't parse the json again. * Note: to correctly load dark mode (-night) resources, make sure you pass Activity as a context (instead of e.g. the application context). * The Activity won't be leaked. - * + *

* Pass null as the cache key to skip caching. */ @WorkerThread diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieConfig.java b/lottie/src/main/java/com/airbnb/lottie/LottieConfig.java new file mode 100644 index 0000000000..2a560dcdf5 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/LottieConfig.java @@ -0,0 +1,69 @@ +package com.airbnb.lottie; + +import com.airbnb.lottie.network.LottieNetworkFetcher; +import com.airbnb.lottie.network.LottieNetworkCacheProvider; + +import java.io.File; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * Class for custom library configuration + */ +public class LottieConfig { + + @Nullable final LottieNetworkFetcher networkFetcher; + @Nullable final LottieNetworkCacheProvider cacheProvider; + + private LottieConfig(@Nullable LottieNetworkFetcher networkFetcher, @Nullable LottieNetworkCacheProvider cacheProvider) { + this.networkFetcher = networkFetcher; + this.cacheProvider = cacheProvider; + } + + public static final class Builder { + + @Nullable + private LottieNetworkFetcher networkFetcher; + @Nullable + private LottieNetworkCacheProvider cacheProvider; + + @NonNull + public Builder setNetworkFetcher(@NonNull LottieNetworkFetcher fetcher) { + this.networkFetcher = fetcher; + return this; + } + + @NonNull + public Builder setCacheDir(@NonNull final File file) { + cacheProvider = new LottieNetworkCacheProvider() { + @Override @NonNull public File getCacheDir() { + if (!file.isDirectory()) { + throw new IllegalArgumentException("cache file must be a directory"); + } + return file; + } + }; + return this; + } + + @NonNull + public Builder setCacheProvider(@NonNull final LottieNetworkCacheProvider fileCacheProvider) { + cacheProvider = new LottieNetworkCacheProvider() { + @NonNull @Override public File getCacheDir() { + File file = fileCacheProvider.getCacheDir(); + if (!file.isDirectory()) { + throw new IllegalArgumentException("cache file must be a directory"); + } + return file; + } + }; + return this; + } + + @NonNull + public LottieConfig build() { + return new LottieConfig(networkFetcher, cacheProvider); + } + } +} \ No newline at end of file diff --git a/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieFetchResult.java b/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieFetchResult.java new file mode 100644 index 0000000000..9a408efe3b --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieFetchResult.java @@ -0,0 +1,73 @@ +package com.airbnb.lottie.network; + +import com.airbnb.lottie.utils.Logger; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class DefaultLottieFetchResult implements LottieFetchResult { + + @NonNull + private final HttpURLConnection connection; + + public DefaultLottieFetchResult(@NonNull HttpURLConnection connection) { + this.connection = connection; + } + + @Override public boolean isSuccessful() { + try { + return connection.getResponseCode() / 100 == 2; + } catch (IOException e) { + return false; + } + } + + @NonNull @Override public InputStream bodyByteStream() throws IOException { + return connection.getInputStream(); + } + + @Nullable @Override public String contentType() { + return connection.getContentType(); + } + + @Nullable @Override public String error() { + try { + return isSuccessful() ? null : + "Unable to fetch " + connection.getURL() + ". Failed with " + connection.getResponseCode() + "\n" + getErrorFromConnection(connection); + } catch (IOException e) { + Logger.warning("get error failed ", e); + return e.getMessage(); + } + } + + @Override public void close() { + connection.disconnect(); + } + + private String getErrorFromConnection(HttpURLConnection connection) throws IOException { + BufferedReader r = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + StringBuilder error = new StringBuilder(); + String line; + + try { + while ((line = r.readLine()) != null) { + error.append(line).append('\n'); + } + } catch (Exception e) { + throw e; + } finally { + try { + r.close(); + } catch (Exception e) { + // Do nothing. + } + } + return error.toString(); + } +} diff --git a/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieNetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieNetworkFetcher.java new file mode 100644 index 0000000000..68b2ac9d16 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/network/DefaultLottieNetworkFetcher.java @@ -0,0 +1,19 @@ +package com.airbnb.lottie.network; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + +import androidx.annotation.NonNull; + +public class DefaultLottieNetworkFetcher implements LottieNetworkFetcher { + + @Override + @NonNull + public LottieFetchResult fetchSync(@NonNull String url) throws IOException { + final HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); + connection.setRequestMethod("GET"); + connection.connect(); + return new DefaultLottieFetchResult(connection); + } +} diff --git a/lottie/src/main/java/com/airbnb/lottie/network/LottieFetchResult.java b/lottie/src/main/java/com/airbnb/lottie/network/LottieFetchResult.java new file mode 100644 index 0000000000..5a1a762125 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/network/LottieFetchResult.java @@ -0,0 +1,39 @@ +package com.airbnb.lottie.network; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +/** + * The result of the operation of obtaining a Lottie animation + */ +public interface LottieFetchResult extends Closeable { + /** + * @return Is the operation successful + */ + boolean isSuccessful(); + + /** + * + * @return Received content stream + */ + @NonNull + InputStream bodyByteStream() throws IOException; + + /** + * + * @return Type of content received + */ + @Nullable + String contentType(); + + /** + * + * @return Operation error + */ + @Nullable + String error(); +} diff --git a/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkCacheProvider.java b/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkCacheProvider.java new file mode 100644 index 0000000000..8258ba61c1 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkCacheProvider.java @@ -0,0 +1,20 @@ +package com.airbnb.lottie.network; + + +import java.io.File; + +import androidx.annotation.NonNull; + +/** + * Interface for providing the custom cache directory where animations downloaded via url are saved. + * @see com.airbnb.lottie.Lottie#initialize + */ +public interface LottieNetworkCacheProvider { + + /** + * Called during cache operations + * + * @return cache directory + */ + @NonNull File getCacheDir(); +} \ No newline at end of file diff --git a/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkFetcher.java new file mode 100644 index 0000000000..48b7658221 --- /dev/null +++ b/lottie/src/main/java/com/airbnb/lottie/network/LottieNetworkFetcher.java @@ -0,0 +1,19 @@ +package com.airbnb.lottie.network; + +import java.io.IOException; + +import androidx.annotation.NonNull; +import androidx.annotation.WorkerThread; + +/** + * Implement this interface to handle network fetching manually when animations are requested via url. By default, Lottie will use an + * HttpUrlConnection under the hood but this enables you to hook into your own network stack. By default, Lottie will also handle caching the + * animations but if you want to provide your own cache directory, you may implement `LottieNetworkCacheProvider`. + * + * @see com.airbnb.lottie.Lottie#initialize + */ +public interface LottieNetworkFetcher { + @WorkerThread + @NonNull + LottieFetchResult fetchSync(@NonNull String url) throws IOException; +} diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java index 71efef2865..f0f29e132e 100644 --- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java +++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkCache.java @@ -1,9 +1,11 @@ package com.airbnb.lottie.network; -import android.content.Context; + +import android.util.Pair; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; -import androidx.core.util.Pair; import com.airbnb.lottie.utils.Logger; @@ -19,10 +21,12 @@ * Helper class to save and restore animations fetched from an URL to the app disk cache. */ public class NetworkCache { - private final Context appContext; - public NetworkCache(Context appContext) { - this.appContext = appContext.getApplicationContext(); + @NonNull + private final LottieNetworkCacheProvider cacheProvider; + + public NetworkCache(@NonNull LottieNetworkCacheProvider cacheProvider) { + this.cacheProvider = cacheProvider; } public void clear() { @@ -139,7 +143,7 @@ private File getCachedFile(String url) throws FileNotFoundException { } private File parentDir() { - File file = new File(appContext.getCacheDir(), "lottie_network_cache"); + File file = cacheProvider.getCacheDir(); if (file.isFile()) { file.delete(); } diff --git a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java index 2f7463373b..21fbc3400e 100644 --- a/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java +++ b/lottie/src/main/java/com/airbnb/lottie/network/NetworkFetcher.java @@ -1,64 +1,52 @@ package com.airbnb.lottie.network; -import android.content.Context; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; -import androidx.core.util.Pair; +import android.util.Pair; import com.airbnb.lottie.LottieComposition; import com.airbnb.lottie.LottieCompositionFactory; import com.airbnb.lottie.LottieResult; import com.airbnb.lottie.utils.Logger; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.zip.ZipInputStream; -public class NetworkFetcher { +import java.util.zip.ZipInputStream; - private final Context appContext; - private final String url; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; - @Nullable private final NetworkCache networkCache; +public class NetworkFetcher { - public static LottieResult fetchSync(Context context, String url, @Nullable String cacheKey) { - return new NetworkFetcher(context, url, cacheKey).fetchSync(); - } + @NonNull + private final NetworkCache networkCache; + @NonNull + private final LottieNetworkFetcher fetcher; - private NetworkFetcher(Context context, String url, @Nullable String cacheKey) { - appContext = context.getApplicationContext(); - this.url = url; - if (cacheKey == null) { - networkCache = null; - } else { - networkCache = new NetworkCache(appContext); - } + public NetworkFetcher(@NonNull NetworkCache networkCache, @NonNull LottieNetworkFetcher fetcher) { + this.networkCache = networkCache; + this.fetcher = fetcher; } + @NonNull @WorkerThread - public LottieResult fetchSync() { - LottieComposition result = fetchFromCache(); + public LottieResult fetchSync(@NonNull String url, @Nullable String cacheKey) { + LottieComposition result = fetchFromCache(url, cacheKey); if (result != null) { return new LottieResult<>(result); } Logger.debug("Animation for " + url + " not found in cache. Fetching from network."); - return fetchFromNetwork(); + + return fetchFromNetwork(url, cacheKey); } - /** - * Returns null if the animation doesn't exist in the cache. - */ @Nullable @WorkerThread - private LottieComposition fetchFromCache() { - if (networkCache == null) { + private LottieComposition fetchFromCache(@NonNull String url, @Nullable String cacheKey) { + if (cacheKey == null) { return null; } Pair cacheResult = networkCache.fetch(url); @@ -80,69 +68,41 @@ private LottieComposition fetchFromCache() { return null; } + @NonNull @WorkerThread - private LottieResult fetchFromNetwork() { - try { - return fetchFromNetworkInternal(); - } catch (IOException e) { - return new LottieResult<>(e); - } - } - - @WorkerThread - private LottieResult fetchFromNetworkInternal() throws IOException { + private LottieResult fetchFromNetwork(@NonNull String url, @Nullable String cacheKey) { Logger.debug("Fetching " + url); - - HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); - connection.setRequestMethod("GET"); - + LottieFetchResult fetchResult = null; try { - connection.connect(); - - if (connection.getErrorStream() != null || connection.getResponseCode() != HttpURLConnection.HTTP_OK) { - String error = getErrorFromConnection(connection); - return new LottieResult(new IllegalArgumentException("Unable to fetch " + url + ". Failed with " + connection.getResponseCode() + "\n" + error)); + fetchResult = fetcher.fetchSync(url); + if (fetchResult.isSuccessful()) { + InputStream inputStream = fetchResult.bodyByteStream(); + String contentType = fetchResult.contentType(); + LottieResult result = fromInputStream(url, inputStream, contentType, cacheKey); + Logger.debug("Completed fetch from network. Success: " + (result.getValue() != null)); + return result; + } else { + return new LottieResult<>(new IllegalArgumentException(fetchResult.error())); } - - LottieResult result = getResultFromConnection(connection); - Logger.debug("Completed fetch from network. Success: " + (result.getValue() != null)); - return result; } catch (Exception e) { return new LottieResult<>(e); } finally { - connection.disconnect(); - } - } - - private String getErrorFromConnection(HttpURLConnection connection) throws IOException { - int responseCode = connection.getResponseCode(); - BufferedReader r = new BufferedReader(new InputStreamReader(connection.getErrorStream())); - StringBuilder error = new StringBuilder(); - String line; - - try { - while ((line = r.readLine()) != null) { - error.append(line).append('\n'); - } - } catch (Exception e) { - throw e; - } finally { - try { - r.close(); - } catch (Exception e) { - // Do nothing. + if (fetchResult != null) { + try { + fetchResult.close(); + } catch (IOException e) { + Logger.warning("LottieFetchResult close failed ", e); + } } } - return error.toString(); } - @Nullable - private LottieResult getResultFromConnection(HttpURLConnection connection) throws IOException { - File file; + @NonNull + private LottieResult fromInputStream(@NonNull String url, @NonNull InputStream inputStream, @Nullable String contentType, + @Nullable String cacheKey) throws IOException { FileExtension extension; LottieResult result; - String contentType = connection.getContentType(); if (contentType == null) { // Assume JSON for best effort parsing. If it fails, it will just deliver the parse exception // in the result which is more useful than failing here. @@ -151,26 +111,37 @@ private LottieResult getResultFromConnection(HttpURLConnectio if (contentType.contains("application/zip")) { Logger.debug("Handling zip response."); extension = FileExtension.ZIP; - if (networkCache == null) { - result = LottieCompositionFactory.fromZipStreamSync(new ZipInputStream(connection.getInputStream()), null); - } else { - file = networkCache.writeTempCacheFile(url, connection.getInputStream(), extension); - result = LottieCompositionFactory.fromZipStreamSync(new ZipInputStream(new FileInputStream(file)), url); - } + result = fromZipStream(url, inputStream, cacheKey); } else { Logger.debug("Received json response."); extension = FileExtension.JSON; - if (networkCache == null) { - result = LottieCompositionFactory.fromJsonInputStreamSync(connection.getInputStream(), null); - } else { - file = networkCache.writeTempCacheFile(url, connection.getInputStream(), extension); - result = LottieCompositionFactory.fromJsonInputStreamSync(new FileInputStream(new File(file.getAbsolutePath())), url); - } + result = fromJsonStream(url, inputStream, cacheKey); } - if (networkCache != null && result.getValue() != null) { + if (cacheKey != null && result.getValue() != null) { networkCache.renameTempFile(url, extension); } + return result; } + + @NonNull + private LottieResult fromZipStream(@NonNull String url, @NonNull InputStream inputStream, @Nullable String cacheKey) + throws IOException { + if (cacheKey == null) { + return LottieCompositionFactory.fromZipStreamSync(new ZipInputStream(inputStream), null); + } + File file = networkCache.writeTempCacheFile(url, inputStream, FileExtension.ZIP); + return LottieCompositionFactory.fromZipStreamSync(new ZipInputStream(new FileInputStream(file)), url); + } + + @NonNull + private LottieResult fromJsonStream(@NonNull String url, @NonNull InputStream inputStream, @Nullable String cacheKey) + throws IOException { + if (cacheKey == null) { + return LottieCompositionFactory.fromJsonInputStreamSync(inputStream, null); + } + File file = networkCache.writeTempCacheFile(url, inputStream, FileExtension.JSON); + return LottieCompositionFactory.fromJsonInputStreamSync(new FileInputStream(new File(file.getAbsolutePath())), url); + } }