패키지, 저장소 및 앱 카테고리 검색

패키지 상세v2.0.0

streaming_shared_preferences

공유된 preferences 기반의 스트림 기반 래퍼로 반응형 키-값 저장을 가능하게 합니다.

AndroidiOSWebmacOSWindowsLinux
30일 다운로드
1.9만
좋아요
122
Pub 점수
150

패키지 설치

flutter pub add streaming_shared_preferences

패키지 개요

공개 정보를 바탕으로 생성한 선택 가이드입니다.

키-값 저장소에 대한 반응형 업데이트를 가능하게 하는 스트림 기반 인터페이스를 제공하는 플러터 패키지입니다. 표준 shared_preferences 플러그인을 래핑하여 스트림을 통해 저장된 데이터의 변경 사항을 실시간으로 감시할 수 있도록 합니다. 이로 인해 개발자는 설정이 변경될 때 자동으로 업데이트되는 반응형 UI를 구축할 수 있으며, 지속적인 로컬 스토리지를 사용하는 앱에서 상태 관리를 향상시킬 수 있습니다. 사용자 설정 변경 시 즉각적인 피드백이 필요한 애플리케이션에 적합합니다.

사용 사례

  • 사용자 선호도에 기반한 실시간 UI 업데이트
  • 반응형 설정 패널
  • 다이나믹 테마 전환
  • 지속적인 앱 상태 동기화
  • 백그라운드 선호 모니터링
  • 스트림을 사용한 크로스 플랫폼 로컬 스토리지

주요 기능

  • 스트림 기반 값 업데이트
  • 반응형 상태 관리
  • 기존 shared_preferences와의 간편한 통합
  • 모든 주요 플랫폼을 지원합니다
  • 최소한의 보일러플레이트
  • 설정 변경 시 자동 반응

적합한 대상

  • 로컬 스토리지에서 실시간 업데이트가 필요한 앱
  • 반응형 프로그래밍 패턴을 사용하는 개발자
  • 동적 사용자 설정이 있는 프로젝트
  • 최소한의 복잡성으로 상태 관리
  • 지속 데이터를 갖춘 크로스 플랫폼 애플리케이션

고려 사항

  • Dart 스트림에 대한 이해가 필요합니다
  • 많은 리스너를 사용할 경우 메모리 사용량이 증가할 수 있습니다
  • 대규모 데이터 세트에 적합하지 않습니다
  • 키-값 쌍으로 제한됨
  • 내부적으로 shared_preferences에 의존합니다
  • 자주 업데이트할 경우 성능에 미치는 영향

원본 README

연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.

streaming_shared_preferences

pub package Build Status Coverage Status

A reactive key-value store for Flutter projects.

streaming_shared_preferences adds reactive functionality on top of shared_preferences. It does everything that regular SharedPreferences does, but it also allows listening to changes in values. This makes it super easy to keep your widgets in sync with persisted values.

Getting started

First, add streaming_shared_preferences into your pubspec.yaml.

If you're already using shared_preferences, you should replace the dependency with streaming_shared_preferences.

dependencies:
  streaming_shared_preferences: ^2.0.0

To get a hold of StreamingSharedPreferences, await on instance:

import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';

...
WidgetsFlutterBinding.ensureInitialized();
final preferences = await StreamingSharedPreferences.instance;

Caveat: The change detection works only in Dart side. This means that if you want to react to changes in values, you should always use StreamingSharedPreferences (not SharedPreferences) to store your values.

Your first streaming preference

Here's the simplest possible plain Dart example on how you would print a value to console every time a counter integer changes:

// Get a reference to the counter value and provide a default value 
// of 0 in case it is null.
Preference<int> counter = preferences.getInt('counter', defaultValue: 0);

// "counter" is a Preference<int> - it can do anything a Stream<int> can.
// We're just going to listen to it and print the value to console.
counter.listen((value) {
  print(value);
});

// Somewhere else in your code, update the value.
counter.setValue(1);

// This is exactly same as above, but the above is more convenient.
preferences.setInt('counter', 2);

The public API follows the same convention as regular SharedPreferences, but every getter returns a Preference object which is a special type of Stream.

Assuming that there's no previously stored value (=it's null), the above example will print 0, 1 and 2 to the console.

Getting a value synchronously

No problem! Just call getValue() on whatever the preferences.getInt(..) (or getString(), getBool(), etc.) returns you.

Connecting values to Flutter widgets

Althought it works perfectly fine with a StreamBuilder, the recommended way is to use the PreferenceBuilder widget.

If you have only one value you need to store in your app, it might make sense to listen to it inline:

class MyCounterWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /// PreferenceBuilder is like StreamBuilder, but with less boilerplate.
    ///
    /// We don't have to provide `initialData` because it can be fetched synchronously
    /// from the provided Preference. There's also no initial flicker when transitioning
    /// between initialData and the stream.
    ///
    /// If you want, you could use a StreamBuilder too.
    return PreferenceBuilder<int>(
      preference: preferences.getInt('counter', defaultValue: 0),
      builder: (BuildContext context, int counter) {
        return Text('Button pressed $counter times!');
      }
    );
  }
}
Use a wrapper class when having multiple preferences

If you have multiple preferences, the recommended approach is to create a class that holds all your Preference objects in a single place:

/// A class that holds [Preference] objects for the common values that you want
/// to store in your app. This is *not* necessarily needed, but it makes your
/// code more neat and fool-proof.
class MyAppSettings {
  MyAppSettings(StreamingSharedPreferences preferences)
      : counter = preferences.getInt('counter', defaultValue: 0),
        nickname = preferences.getString('nickname', defaultValue: '');

  final Preference<int> counter;
  final Preference<String> nickname;
}

In our app entry point, you'll obtain an instance to StreamingSharedPreferences once and pass that to our settings class. Now we can pass MyAppSettings down to the widgets that use it:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  /// Obtain instance to streaming shared preferences, create MyAppSettings, and
  /// once that's done, run the app.
  final preferences = await StreamingSharedPreferences.instance;
  final settings = MyAppSettings(preferences);
  
  runApp(MyApp(settings));
}

This makes the calling code become quite neat:

class MyCounterWidget extends StatelessWidget {
  MyCounterWidget(this.settings);
  final MyAppSettings settings;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        PreferenceBuilder<String>(
          preference: settings.nickname,
          builder: (context, nickname) => Text('Hey $nickname!'),
        ),
        PreferenceBuilder<int>(
          preference: settings.counter,
          builder: (context, counter) => Text('You have pushed the button $counter times!'),
        ),
        FloatingActionButton(
          onPressed: () {
            /// To obtain the current value synchronously, we can call ".getValue()".
            final currentCounter = settings.counter.getValue();

            /// To update the value, we can call ".setValue()" - no need to provide a key!
            /// Alternatively, we could just call "preferences.setInt('counter', currentCounter + 1)".
            settings.counter.setValue(currentCounter + 1);
          },
          child: Icon(Icons.add),
        ),
      ],
    );
  }
}

You can see a full working example of this in the example project.

When your widget hierarchy becomes deep enough, you would want to pass MyAppSettings around with an InheritedWidget or provider instead.

"But what about muh abstraction!"

If you're all about the clean architecture and don't want to pollute your domain layer with Preference objects from a third-party library by some random internet stranger, all the power to you.

Here's one way to make Uncle Bob proud.

/// The contract for persistent values in your app that can be shared
/// to your pure business logic classes
abstract class SettingsContract {
  Stream<int> streamCounterValues();
  void setCounter(int value);
}

/// ... somewhere else in your codebase
class MyBusinessLogic {
  MyBusinessLogic(SettingsContract settings) {
    // Do something with "streamCounterValues()" and "setCounter()" along with
    // whatever your business use case needs
  }
}

No StreamingSharedPreferences specifics went in there.

If for some reason you want to switch into some other library (or get rid of this library altogether), you can do so without modifying your business logic.

Here's how the implementation based on StreamingSharedPreferences would look like:

/// One implementation of SettingsContract backed by StreamingSharedPreferences
class MyAppSettings implements SettingsContract {
  MyAppSettings(StreamingSharedPreferences preferences)
      : counter = preferences.getInt('counter', defaultValue: 0);

  final Preference<int> _counter;

  @override
  Stream<int> streamCounterValues() => _counter;

  @override
  void setCounter(int value) => _counter.setValue(value);
}

Storing custom types with JsonAdapter

The entire library is built to support storing custom data types easily with a PreferenceAdapter. In fact, every built-in type has its own PreferenceAdapter - so every type is actually a custom value.

For most cases, there's a convenience adapter that handles common pitfalls when storing and retrieving custom values called JsonAdapter. It helps you to store your values in JSON and it also saves you from duplicating if (value == null) return null for your custom adapters.

For example, if we have a class called SampleObject:

class SampleObject {
  SampleObject(this.isAwesome);
  final bool isAwesome;

  SampleObject.fromJson(Map<String, dynamic> json) :
    isAwesome = json['isAwesome'];

  Map<String, dynamic> toJson() => { 'isAwesome': isAwesome };
}

As seen from the above example, SampleObject implements both fromJson and toJson.

When the toJson method is present, JsonAdapter will call toJson automatically. For reviving, you need to provide a deserializer that calls fromJson manually:

final sampleObject = preferences.getCustomValue<SampleObject>(
  'my-key',
  defaultValue: SampleObject.empty(),
  adapter: JsonAdapter(
    deserializer: (value) => SampleObject.fromJson(value),
  ),
);

Depending on your use case, you need to provide a non-null SampleObject.empty() that represents a sane default for your custom type when the value is not loaded just yet.

Using JsonAdapter with built_value

You can do custom serialization logic before JSON encoding the object by providing a serializer. Similarly, you can use deserializer to map the decoded JSON map into any object you want.

For example, if the previous SampleObject didn't have toJson and fromJson methods, but was a built_value model instead:

final sampleObject = preferences.getCustomValue<SampleObject>(
  'my-key',
  defaultValue: SampleObject.empty(),
  adapter: JsonAdapter(
    serializer: (value) => serializers.serialize(value),
    deserializer: (value) => serializers.deserialize(value),
  ),
);

The serializers here is your global serializer that comes from built_value.

카테고리

의존성

의존성 목록 2 개

소스 저장소

이 패키지를 사용하는 프로젝트