Stock은 Flutter 애플리케이션에서 효율적인 비동기 데이터 로딩 및 캐싱을 위한 Dart 패키지입니다. 데이터베이스나 메모리 캐시와 같은 로컬 데이터 소스를 원격 네트워크 요청과 통합하는 것을 단순화하여 데이터 일관성과 성능을 보장합니다. 통합된 인터페이스를 제공함으로써 개발자는 안전하게 데이터를 가져오고 캐시하며 다시 로드할 수 있으며, 반복 코드를 줄이고 오래된 데이터 또는 네트워크 실패 등의 예외 상황을 처리할 수 있습니다. 신뢰할 수 있는 오프라인 지원과 실시간 업데이트가 필요한 앱에 적합합니다.
사용 사례
오프라인 우선 데이터 가져오기
API 응답 캐싱
로컬 및 원격 데이터 동기화
실시간 데이터 업데이트
중복 네트워크 호출 줄이기
캐시된 데이터로 앱 상태 관리
주요 기능
통합된 로컬 및 네트워크 데이터 액세스
자동 캐시 무효화
비동기 데이터 스트림 지원
내장된 재시도 및 백업 메커니즘
가볍고 비침습적
기존 데이터 레이어와의 원활한 통합
적합한 대상
강력한 오프라인 기능이 필요한 앱
간편한 데이터 관리를 원하는 개발자
자주 API 상호작용이 발생하는 프로젝트
유지보수 가능한 데이터 논리를 우선시하는 팀
반응형 프로그래밍 패턴을 사용하는 Flutter 앱
장치 상태 간에 일관된 데이터가 필요한 응용 프로그램
고려 사항
async/await 패턴에 대한 이해가 필요합니다
복잡한 캐시 전략을 위해 추가 설정이 필요할 수 있습니다
완전한 ORM 또는 데이터베이스 솔루션이 아님
읽기 중심 작업에 가장 적합
복잡한 쿼리 필터링에 대한 내장 지원이 제한적
지속성을 위해 외부 저장 구현에 의존합니다
원본 README
연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.
Stock is a dart package for loading data from both remote and local sources.
It is inspired by the Store Kotlin library.
Its main goal is to prevent excessive calls to the network and disk cache.
By utilizing it, you eliminate the possibility of flooding your network with the same request while, at the same time, adding layers of caching.
Although you can use it without a local source, the greatest benefit comes from combining Stock with a local database such as Floor, Drift, Sqflite, Realm, etc.
Features
Combine local (DB, cache) and network data simply. It provides a data Stream where you can listen and work with your data.
Know the data Stream state. It's useful for displaying errors or loading indicators, for example.
If you are not using a local DB, Stock provides a memory cache, to share and improve upon your app's experience.
Work with your data more safely. If an error is thrown, Stock will catch it and return it into the stream so it can be handled easily.
Overview
A Stock is responsible for managing a particular data request.
It is based on two important classes:
Fetcher: defines how data will be fetched over the network.
SourceOfTruth: defines how local data will be read and written in your local cache. Although Stock can be used without it, we recommend its usage.
Stock uses generic keys as identifiers for data.
A key can be any value object that properly implements toString(), equals() and hashCode().
When you create Stock, it provides you with a bunch of methods in order to access the data.
The most important one is stream(), which provides you with a Stream of your data, which can be used to update your UI or to do a specific action.
The Fetcher is required to fetch new data from the network.
You can create it from a Future or from a Stream.
FutureFetcher is usually used alongside a RestApi, whereas StreamFetcher is used with a Stream source like a Web Socket.
final futureFetcher = Fetcher.ofFuture<String, List<Tweet>>(
(userId) => _api.getUserTweets(userId),
);
final streamFetcher = Fetcher.ofStream<String, List<Tweet>>(
(userId) => _api.getUserTweetsStream(userId),
);
2. Create a Source Of Truth
The SourceOfTruth is used to read and write the remote data in a local cache.
Generally you will implement the SourceOfTruth using a local database. However, if you are not using a local database / cache, the library provides the CachedSourceOfTruth, a source of truth which stores the data in memory.
final sourceOfTruth = SourceOfTruth<String, List<Tweet>>(
reader: (userId) => _database.getUserTweets(userId),
writer: (userId, tweets) => _database.writeUserTweets(userId, tweets),
delete: (userId) => _database.deleteUserTweets(userId), // this is optional
deleteAll: () => _database.deleteAllTweets(), // this is optional
);
In this example, Fetcher type is exactly the SourceOfTruth type. If you need to use multiple types, you can use the mapTo extension.
Note: to the proper operation, when write is invoked with new data, the source of truth has to emit the new value in the reader.
3. Create the Stock
Stock lets you combine the different data sources and get the data.
final stock = Stock<String, List<Tweet>>(
fetcher: fetcher,
sourceOfTruth: sourceOfTruth,
);
Get a data Stream from Stock
You can generate a data Stream using stream().
You need to invoke it with a specific key, and an optional refresh value that tells Stock if a refresh is optional or mandatory.
That returns a data stream of StockResponse, which has 3 possible values:
StockResponseLoading informs that a network request is in progress. It can be useful to display a loading indicator in your UI.
StockResponseData holds the response data. It has a value field which includes an instance of the type returned by Stock.
StockResponseError indicates that an error happened.
When an error happens, Stock does not throw an exception, instead, it wraps it in this class.
It includes an error field that contains the exception thrown by the given origin.
Each StockResponse includes an origin field which specifies where the event is coming from.
Stock provides a couple of methods to get data without using a data stream.
get returns cached data -if it is cached- otherwise will return fresh/network data (updating your caches).
fresh returns fresh data updating your cache
clear purge a particular entry from memory and disk cache
clearAll Purge all entries from memory and disk cache
// Get fresh datafinalList<Tweet> freshTweets = await stock.fresh(key);
// Get the previous cached datafinalList<Tweet> cachedTweets = await stock.get(key);
// Clear key from stockawait stock.clear(key);
// Clear all keys from stockawait stock.clearAll();
Use different types for Fetcher and SourceOfTruth
Sometimes you need to use different entities for Network and DB. For that case Stock provides the StockTypeMapper, a class that transforms one entity into the other.
StockTypeMapper is used in the SourceOfTruth via the method mapToUsingMapper
Sometimes your Source of Truth does not provide you with a real-time data stream.
For example, suppose that you are using shared_preferences to store your data, or you are just catching your data in memory.
For these cases, Stock provides you the CachedSourceOfTruth, a SourceOfTruth that be helpful in these cases.
classSharedPreferencesSourceOfTruthextendsCachedSourceOfTruth<String, String> {
SharedPreferencesSourceOfTruth();
@override@protectedStream<T?> reader(String key) async* {
final prefs = awaitSharedPreferences.getInstance();
// Read data from an non-stream sourcefinal stringValue = prefs.getString(key);
setCachedValue(key, stringValue);
yield* super.reader(key);
}
@override@protectedFuture<void> write(String key, String? value) async {
final prefs = awaitSharedPreferences.getInstance();
await prefs.setString(key, value);
awaitsuper.write(key, value);
}
}
This class can be used with StockTypeMapper to transform your data into an entity.
// The mapper json to transform a string into a User finalStockTypeMapper<String, User> mapper = _createUserMapper();
finalSharedPreferencesSourceOfTruth<String, User> = SharedPreferencesSourceOfTruth()
.mapToUsingMapper(mapper);
Copyright (c) 2022 Xmartlabs SRL.
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.