Skip to content

Commit

Permalink
release version 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
dungngminh committed Dec 14, 2024
1 parent 1c17bcb commit fc2530c
Show file tree
Hide file tree
Showing 17 changed files with 359 additions and 64 deletions.
7 changes: 7 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
version: 2
enable-beta-ecosystems: true
updates:
- package-ecosystem: "pub"
directory: "/"
schedule:
interval: "weekly"
32 changes: 32 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Build

on:
pull_request:
branches:
- main
paths-ignore:
- "**.md"
push:
branches:
- main
paths-ignore:
- "**.md"

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Dart
uses: dart-lang/setup-dart@v1

- name: Get dependencies
run: dart pub get

- name: Analyze
run: dart analyze

- name: Test
run: dart test --coverage=coverage
21 changes: 21 additions & 0 deletions LiCENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2024 Nguyen Minh Dung

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
A sample command-line application with an entrypoint in `bin/`, library code
in `lib/`, and example unit test in `test/`.
# Dart Google Sheet Remote Config

This is a Dart package that allows you to use Google Sheets as a remote config for your Dart/Flutter app. It is inspired by @theapache64's [blog](https://theapache64.github.io/posts/google-sheet-as-remote-config-for-microcontrollers/).

## Why Google Sheets?

## How to use
3 changes: 3 additions & 0 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
# The core lints are also what is used by pub.dev for scoring packages.

include: package:lints/recommended.yaml
analyzer:
errors:
pattern_never_matches_value_type: ignore

# Uncomment the following section to specify additional rules.

Expand Down
5 changes: 0 additions & 5 deletions example/bin/example.dart

This file was deleted.

33 changes: 31 additions & 2 deletions example/lib/example.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
int calculate() {
return 6 * 7;
import 'package:dart_gsheet_remote_config/dart_gsheet_remote_config.dart';
import 'package:version/version.dart';

Future<void> main() async {
final remoteConfig = SheetRemoteConfig();

await remoteConfig.initilize(
id: "1qEskeRwdtAfnewig-slspPHKafDKV0JMexXdDWgCCeQ");

final testValue = remoteConfig.getDouble("test");
print("test: $testValue");

final themeMode = remoteConfig.getString("themeMode");

print("themeMode: $themeMode");

final enableAds = remoteConfig.getBool("enableAds");

print("enableAds: $enableAds");

final inAppVersion = Version.parse("1.0.0");

final currentVersion = remoteConfig.getString("currentVersion");

print("currentVersion: $currentVersion");

if (currentVersion != null && inAppVersion < Version.parse(currentVersion)) {
print("Please update your app");
} else {
print("You are using the latest version");
}
}
8 changes: 8 additions & 0 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version:
dependency: "direct main"
description:
name: version
sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
vm_service:
dependency: transitive
description:
Expand Down
3 changes: 3 additions & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ name: example
description: A sample command-line application.
version: 1.0.0
# repository: https://github.com/my_org/my_repo
publish_to: "none" #

environment:
sdk: ^3.5.4
Expand All @@ -12,6 +13,8 @@ dependencies:
dart_gsheet_remote_config:
path: ../

version: ^3.0.2

dev_dependencies:
lints: ^4.0.0
test: ^1.24.0
8 changes: 0 additions & 8 deletions example/test/example_test.dart

This file was deleted.

2 changes: 1 addition & 1 deletion lib/dart_gsheet_remote_config.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export 'dart_gsheet_remote_config.dart';
export 'src/sheet_remote_config.dart';
export 'src/sheet_remote_config_exception.dart';
123 changes: 85 additions & 38 deletions lib/src/sheet_remote_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,18 @@ class SheetRemoteConfig {
final http.Client _client;

/// In-memory cache for the config values.
final _inMemoryCachedConfig = <String, dynamic>{};
final _inMemoryCachedConfig = <String, String>{};

/// Fetches the config from the given URL and stores it in the in-memory cache.
///
/// *Parameters:*
///
/// [configUrl] - The URL to fetch the config from.
/// [id] - The ID of the Google Sheet.
/// You can find the ID of the Google Sheet in the URL of the sheet.
/// For example, if the URL is `https://docs.google.com/spreadsheets/d/1a2b3c4d5e6f7g8h9i0j`, the ID is `1a2b3c4d5e6f7g8h9i0j`.
///
/// [sheetName] - The name of the sheet to fetch the config from.
/// If the sheet name is not provided, the first sheet in the Google Sheet will be used.
///
/// *Exceptions:*
///
Expand All @@ -36,26 +40,55 @@ class SheetRemoteConfig {
try {
final requestUri = _buildSheetUri(id: id, sheetName: sheetName);
final response = await _client.get(requestUri);
if (response.statusCode == 200) {
final payload = response.body;
} else {
if (response.statusCode != 200) {
throw Exception('Failed to load remote config');
}
} catch (e) {
final payload = response.body;
if (!payload.startsWith('"')) {
throw SheetRemoteConfigException(
message: 'Invalid remote config', stackTrace: StackTrace.current);
}
// "key","value"
final keyValues = payload.split('\n').map((e) {
final entries = e.split(',');
final key = entries[0].replaceAll('"', '');
final value = entries[1].replaceAll('"', '');
return MapEntry(key, value);
}).toList();
_inMemoryCachedConfig.addAll(Map.fromEntries(keyValues));
} catch (e, st) {
throw SheetRemoteConfigException(
message: switch (e) {
FormatException() => 'Invalid remote config',
_ => 'Error when handling: $e',
});
FormatException() => 'Invalid remote config',
_ => 'Error when handling: $e',
},
stackTrace: st);
}
}

/// Builds a URI for accessing a Google Sheet in **CSV format**.
///
/// This function constructs a URI that points to a specific Google Sheet
/// and optionally to a specific sheet within the spreadsheet. The URI
/// is formatted to request the sheet data in **CSV format**.
///
/// The function filters out any null values from the query parameters.
///
/// *Parameters:*
///
/// - [id] is the unique identifier of the Google Sheet.
///
/// - [sheetName] is the optional name of the specific sheet within the spreadsheet.
///
/// *Returns:*
///
/// Returns a [Uri] object that can be used to access the Google Sheet data.
Uri _buildSheetUri({required String id, String? sheetName}) {
return Uri.https(
'docs.google.com',
'/spreadsheets/d/$id/gviz/tq?',
'/spreadsheets/d/$id/gviz/tq',
{
'tqx': 'out:json',
'tqx': 'out:csv',
'sheet': sheetName,
}.filterValues((value) => value != null));
}
Expand All @@ -77,7 +110,7 @@ class SheetRemoteConfig {
///
/// ```dart
/// final config = SheetRemoteConfig();
/// await config.initilize(configUrl: 'http://example.com/config'); // {"key1": "value1", "key2": true, "key3": 1.0, "key4": 100, "key5": { "key6": "value5" }}
/// await config.initialize(id: 'eqweqdqdadqweqdfwsdf'); // "key1","value1"\n"key2","true"\n"key3","1.0"\n"key4","100"
///
/// final value = config.getString('key1');
/// print(value); // value1
Expand All @@ -91,22 +124,19 @@ class SheetRemoteConfig {
/// final value4 = config.getInt('key4');
/// print(value4); // 100
///
/// final value5 = config.getMap('key5');
/// print(value5); // { "key6": "value5" }
///
/// final value6 = config.getString('key7');
/// final value6 = config.getString('key5');
/// print(value6); // null
///
/// final value6Default = config.getString('key7', defaultValue: 'default value');
/// final value6Default = config.getString('key5', defaultValue: 'default value');
/// print(value6Default); // default value
/// ```
/// {@endtemplate}
T? _get<T>(String key, {T? defaultValue}) {
String? _get(String key) {
try {
final value = _inMemoryCachedConfig[key] as T?;
return value ?? defaultValue;
final value = _inMemoryCachedConfig[key];
return value;
} catch (e) {
return defaultValue;
return null;
}
}

Expand All @@ -116,7 +146,12 @@ class SheetRemoteConfig {
///
/// {@macro get_value}
String? getString(String key, {String? defaultValue}) {
return _get<String>(key, defaultValue: defaultValue);
try {
final valueAtKey = _get(key);
return valueAtKey ?? defaultValue;
} catch (e) {
return defaultValue;
}
}

/// Return the value of the given key from the in-memory cache as a [bool].
Expand All @@ -125,7 +160,17 @@ class SheetRemoteConfig {
///
/// {@macro get_value}
bool? getBool(String key, {bool? defaultValue}) {
return _get<bool>(key, defaultValue: defaultValue);
try {
final value = _get(key);
if (value == null && value!.isEmpty) return defaultValue;
final boolValue = bool.tryParse(
value,
caseSensitive: false,
);
return boolValue ?? defaultValue;
} catch (e) {
return defaultValue;
}
}

/// Return the value of the given key from the in-memory cache as an [int].
Expand All @@ -134,7 +179,14 @@ class SheetRemoteConfig {
///
/// {@macro get_value}
int? getInt(String key, {int? defaultValue}) {
return _get<int>(key, defaultValue: defaultValue);
try {
final valueAtKey = _inMemoryCachedConfig[key];
if (valueAtKey == null && valueAtKey!.isEmpty) return defaultValue;
final intValue = int.tryParse(valueAtKey);
return intValue ?? defaultValue;
} catch (e) {
return defaultValue;
}
}

/// Return the value of the given key from the in-memory cache as a [double].
Expand All @@ -143,26 +195,21 @@ class SheetRemoteConfig {
///
/// {@macro get_value}
double? getDouble(String key, {double? defaultValue}) {
return _get<double>(key, defaultValue: defaultValue);
}

/// Return the value of the given key from the in-memory cache as a [Map].
///
/// If the key is not found, return the default value.
///
/// {@macro get_value}
Map<String, dynamic>? getMap(
String key, {
Map<String, dynamic>? defaultValue,
}) {
return _get<Map<String, dynamic>>(key, defaultValue: defaultValue);
try {
final valueAtKey = _inMemoryCachedConfig[key];
if (valueAtKey == null && valueAtKey!.isEmpty) return defaultValue;
final doubleValue = double.tryParse(valueAtKey);
return doubleValue ?? defaultValue;
} catch (e) {
return defaultValue;
}
}

/// Return all the values from the in-memory cache.
///
/// Returns:
/// A [Map] containing all the values from the in-memory cache.
Map<String, dynamic> getAll() {
Map<String, String> getAll() {
return _inMemoryCachedConfig;
}
}
Expand Down
8 changes: 2 additions & 6 deletions lib/src/sheet_remote_config_exception.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// A [SheetRemoteConfigException] class for handling errors related to the Simple Remote Config.
/// A [SheetRemoteConfigException] class for handling errors related to the [SheetRemoteConfig].
///
/// This exception can be used to indicate various issues that may arise when
/// working with the Simple Remote Config, such as network errors, parsing errors,
/// working with the [SheetRemoteConfig], such as network errors, parsing errors,
/// or configuration issues.
///
/// Example usage:
Expand Down Expand Up @@ -29,7 +29,3 @@ class SheetRemoteConfigException implements Exception {
return 'SheetRemoteConfigException: $message';
}
}

class UnauthorizedException extends SheetRemoteConfigException {
UnauthorizedException({super.message = 'Unauthorized'});
}
Loading

0 comments on commit fc2530c

Please sign in to comment.