diff --git a/docs/concepts/action_parsers.mdx b/docs/concepts/action_parsers.mdx deleted file mode 100644 index 0b290b0a..00000000 --- a/docs/concepts/action_parsers.mdx +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: "Action Parsers" -description: "Documentation for Action Parsers" ---- - -Stac supports a variety of built-in action parsers to handle different types of actions. However, you can also create custom action parsers to handle custom actions or actions that are not supported by Stac out of the box. This guide will walk you through the basics of creating and using action parsers in Stac. - -## What is a Stac Action Parser? - -A Stac action parser is a custom class that interprets specific JSON objects representing actions and executes the corresponding logic in your Flutter application. This allows for highly flexible and customizable behavior tailored to the specific needs of your application. - -## Creating a Custom Action Parser - -To create a custom action parser, you need to follow these steps: - -1. **Define the JSON Structure**: Define the structure of the JSON object that your action parser will interpret. This structure should be well-documented and easy to understand. -2. **Create the Action Parser Class**: Create a new Dart class that implements the `StacActionParser` interface provided by Stac. This class will contain the logic to interpret the JSON object and execute the corresponding action. -3. **Register the Action Parser**: Register the custom action parser with Stac so that it can be used to interpret JSON objects. - -## Example Action Parser - -In this example, we will create a custom action parser that calls a print function with a provided message. - -### Step 1: Define the JSON Structure - -The JSON structure for the custom action might look like this: - -```json -{ - "actionType": "print", - "message": "Hello, Stac!" -} -``` -For this JSON Structure, we can create a data class to represent the custom action and to provide the `fromJson` method to convert the JSON object to the custom action object. - -```dart -@freezed -abstract class PrintAction with _$PrintAction { - const factory PrintAction({ - required String message, - }) = _PrintAction; - - factory PrintAction.fromJson(Map json) => - _$PrintActionFromJson(json); -} -``` -:::note -Here we are using the freezed package to create the data class. But you can use any other method to create the data class. -::: - -### Step 2: Create the Action Parser Class - -Next, we create a custom action parser class that implements the `StacActionParser` interface. This class will interpret the JSON object and execute the corresponding logic. - -```dart -class PrintActionParser implements StacActionParser { - @override - String get actionType => 'print'; - - @override - PrintAction getModel(Map json) => PrintAction.fromJson(json); - - @override - FutureOr onCall(BuildContext context, StacNavigateAction model) { - print(model.message); - } -} -``` - -### Step 3: Register the Action Parser - -Finally, you need to register the custom action parser with Stac. - -There are 2 ways to register the custom action parser: - -1. **Register in `Stac.initialize`**: You can register the parser when initializing Stac by passing it in the `actionParser` parameter. - -```dart -void main() async { - await Stac.initialize( - actionParsers: const [ - PrintActionParser(), - ], - ); - runApp(const MyApp()); -} -``` - -2. **Register through StacRegistry**: You can also register the action parser anywhere using the `StacRegistry` class. - -`StacRegistry` provides you with two method to register the action parser. - -1. Register a single action parser: - -```dart -StacRegistry.instance.registerAction(parser); -``` - -2. Register multiple action parsers: - -```dart -StacRegistry.instance.registerAllActions([ - StacShareParser(), - StacBluetoothParser(), -]); -``` - -## Conclusion - -Creating custom action parsers in Stac not only allows you to extend the functionality of the library but also enables you to define highly customizable behaviors within your application. By defining custom parsers for actions, you can leverage the full power of server-driven UI in your Flutter application, ensuring dynamic and responsive user interactions. diff --git a/docs/concepts/custom_actions.mdx b/docs/concepts/custom_actions.mdx new file mode 100644 index 00000000..cab8187b --- /dev/null +++ b/docs/concepts/custom_actions.mdx @@ -0,0 +1,267 @@ +--- +title: "Building Custom Actions" +description: "Learn how to create custom StacAction classes that extend Stac functionality and enable custom behaviors in your server-driven UI" +--- + +While Stac provides various built-in actions (navigation, dialogs, network requests, etc.), you may need to create custom actions for your specific use cases. This guide walks you through creating custom StacAction classes that work seamlessly with Stac's JSON serialization and parser system. + +## What is a Custom StacAction? + +A custom StacAction is a Dart class that: +- Extends the `StacAction` base class +- Can be serialized to and deserialized from JSON +- Works with Stac's action parser system to execute behaviors +- Can be triggered from widgets in your JSON definitions + +Custom actions enable you to: +- Integrate third-party services and APIs +- Implement business-specific logic +- Extend Stac's action functionality beyond built-in actions +- Create reusable behaviors for your application + +## Prerequisites + +Before creating a custom action, ensure you have: + +1. **Dependencies**: `stac_core` and `json_annotation` packages +2. **Code Generation**: `build_runner` for generating JSON serialization code +3. **Parser**: A corresponding action parser to execute your action. + +## Step-by-Step Guide + +### Step 1: Define Your Action Class + +Create a new file (e.g., `lib/actions/stac_share_action.dart`) and define your action class: + +```dart +import 'package:json_annotation/json_annotation.dart'; +import 'package:stac_core/core/stac_action.dart'; +import 'package:stac_core/foundation/specifications/action_type.dart'; + +part 'stac_share_action.g.dart'; + +@JsonSerializable() +class StacShareAction extends StacAction { + const StacShareAction({ + required this.text, + this.subject, + this.title, + }); + + final String text; + final String? subject; + final String? title; + + @override + String get actionType => 'share'; + + factory StacShareAction.fromJson(Map json) => + _$StacShareActionFromJson(json); + + @override + Map toJson() => _$StacShareActionToJson(this); +} +``` + +### Step 2: Required Components + +Every custom StacAction must include: + +#### 1. **Part File Declaration** + +```dart +part 'stac_share_action.g.dart'; +``` + +This enables code generation for JSON serialization. + +#### 2. **JsonSerializable Annotation** + +```dart +@JsonSerializable() +``` + +For nested actions or complex types, use `explicitToJson: true`: + +```dart +@JsonSerializable(explicitToJson: true) +``` + +#### 3. **Action Type Getter** + +```dart +@override +String get actionType => 'share'; +``` + +This unique identifier is used in JSON: `{"actionType": "share"}`. + +#### 4. **Constructor** + +```dart +const StacShareAction({ + required this.text, + this.subject, +}); +``` + +The base `StacAction` constructor handles JSON data internally. + +#### 5. **fromJson Factory Constructor** + +```dart +factory StacShareAction.fromJson(Map json) => + _$StacShareActionFromJson(json); +``` + +#### 6. **toJson Method** + +```dart +@override +Map toJson() => _$StacShareActionToJson(this); +``` + +### Step 3: Generate Code + +Run code generation to create the `*.g.dart` file: + +```bash +flutter pub run build_runner build --delete-conflicting-outputs +``` + +This generates `stac_share_action.g.dart` with the serialization logic. + +### Step 4: Create an Action Parser + +To execute your action, create an action parser: + +```dart +import 'package:flutter/material.dart'; +import 'package:stac_framework/stac_framework.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:your_app/actions/stac_share_action.dart'; + +class StacShareActionParser implements StacActionParser { + const StacShareActionParser(); + + @override + String get actionType => 'share'; + + @override + StacShareAction getModel(Map json) => + StacShareAction.fromJson(json); + + @override + FutureOr onCall(BuildContext context, StacShareAction model) async { + return await Share.share( + model.text, + subject: model.subject, + ); + } +} +``` + +### Step 5: Register the Action Parser + +Register your action parser during Stac initialization: + +```dart +void main() async { + await Stac.initialize( + options: defaultStacOptions, + actionParsers: const [ + StacShareActionParser(), + ], + ); + runApp(const MyApp()); +} +``` + +## Advanced Patterns + +### Using Converters + +Similar to widgets, actions can use converters for special types: + +#### DoubleConverter + +For `double` fields that may come as integers in JSON: + +```dart +import 'package:stac_core/core/converters/double_converter.dart'; + +@JsonSerializable() +class StacCustomAction extends StacAction { + const StacCustomAction({ + this.duration, + }); + + @DoubleConverter() + final double? duration; + + @override + String get actionType => 'customAction'; + + // ... fromJson and toJson +} +``` + +## Using Custom Actions + +**In Dart (stac/ folder)** + +```dart +import 'package:stac_core/stac_core.dart'; +import 'package:your_app/actions/stac_share_action.dart'; + +@StacScreen(screenName: 'article') +StacWidget articleScreen() { + return StacScaffold( + body: StacColumn( + children: [ + StacText(data: 'Article Title'), + StacElevatedButton( + onPressed: StacShareAction( + text: 'Check out this article!', + subject: 'Article', + ).toJson(), + child: StacText(data: 'Share'), + ), + ], + ), + ); +} +``` + +After `stac build` or `stac deploy`, your generated json looks like this: + +```json +{ + "type": "elevatedButton", + "onPressed": { + "actionType": "share", + "text": "Check out this article!", + "subject": "Article" + }, + "child": { + "type": "text", + "data": "Share" + } +} +``` + +Actions are commonly used in widget `onPressed`, `onTap`, and other callback properties. For example: + +```json +{ + "type": "gestureDetector", + "onTap": { + "actionType": "share", + "text": "Shared content" + }, + "child": { + "type": "text", + "data": "Tap to share" + } +} +``` \ No newline at end of file diff --git a/docs/concepts/custom_widgets.mdx b/docs/concepts/custom_widgets.mdx new file mode 100644 index 00000000..9c030007 --- /dev/null +++ b/docs/concepts/custom_widgets.mdx @@ -0,0 +1,294 @@ +--- +title: "Building Custom Widgets" +description: "Learn how to create custom StacWidget classes that extend Stac functionality and integrate seamlessly with the Stac ecosystem" +--- + +While Stac provides 70+ built-in widgets, you may need to create custom widgets for your specific use cases. This guide walks you through creating custom StacWidget classes that work seamlessly with Stac's JSON serialization and parser system. + +## What is a Custom StacWidget? + +A custom StacWidget is a Dart class that: +- Extends the `StacWidget` base class +- Can be serialized to and deserialized from JSON +- Works with Stac's parser system to render Flutter widgets +- Can be used in your `/stac` folder and deployed to Stac Cloud + +Custom widgets enable you to: +- Wrap third-party Flutter packages +- Create reusable UI components specific to your app +- Extend Stac's functionality beyond built-in widgets +- Build domain-specific widgets for your business logic + +## Prerequisites + +Before creating a custom widget, ensure you have: + +1. **Dependencies**: `stac_core` and `json_annotation` packages +2. **Code Generation**: `build_runner` for generating JSON serialization code +3. **Parser**: A corresponding parser to render your widget. + +## Step-by-Step Guide + +### Step 1: Define Your Widget Class + +Create a new file (e.g., `lib/widgets/stac_custom_badge.dart`) and define your widget class: + +```dart +import 'package:json_annotation/json_annotation.dart'; +import 'package:stac_core/core/stac_widget.dart'; +import 'package:stac_core/foundation/foundation.dart'; + +part 'stac_custom_badge.g.dart'; + +@JsonSerializable() +class StacCustomBadge extends StacWidget { + const StacCustomBadge({ + required this.text, + this.color, + this.size, + this.child, + }); + + final String text; + final StacColor? color; + final double? size; + final StacWidget? child; + + @override + String get type => 'customBadge'; + + factory StacCustomBadge.fromJson(Map json) => + _$StacCustomBadgeFromJson(json); + + @override + Map toJson() => _$StacCustomBadgeToJson(this); +} +``` + +### Step 2: Required Components + +Every custom StacWidget must include: + +#### 1. **Part File Declaration** + +```dart +part 'stac_custom_badge.g.dart'; +``` + +This enables code generation for JSON serialization. + +#### 2. **JsonSerializable Annotation** + +```dart +@JsonSerializable() +``` + +For nested widgets, use `explicitToJson: true`: + +```dart +@JsonSerializable(explicitToJson: true) +``` + +#### 3. **Type Getter** + +```dart +@override +String get type => 'customBadge'; +``` + +This unique identifier is used in JSON: `{"type": "customBadge"}`. + +#### 4. **fromJson Factory Constructor** + +```dart +factory StacCustomBadge.fromJson(Map json) => + _$StacCustomBadgeFromJson(json); +``` + +#### 5. **toJson Method** + +```dart +@override +Map toJson() => _$StacCustomBadgeToJson(this); +``` + +### Step 3: Generate Code + +Run code generation to create the `*.g.dart` file: + +```bash +flutter pub run build_runner build --delete-conflicting-outputs +``` + +This generates `stac_custom_badge.g.dart` with the serialization logic. + +### Step 4: Create a Parser + +To render your widget, create a parser: + +```dart +import 'package:flutter/material.dart'; +import 'package:stac_framework/stac_framework.dart'; +import 'package:your_app/widgets/stac_custom_badge.dart'; + +class StacCustomBadgeParser extends StacParser { + const StacCustomBadgeParser(); + + @override + String get type => 'customBadge'; + + @override + StacCustomBadge getModel(Map json) => + StacCustomBadge.fromJson(json); + + @override + Widget parse(BuildContext context, StacCustomBadge model) { + return Container( + padding: EdgeInsets.all(model.size ?? 8), + decoration: BoxDecoration( + color: model.color?.toColor() ?? Colors.blue, + borderRadius: BorderRadius.circular(4), + ), + child: Text(model.text), + ); + } +} +``` + +### Step 5: Register the Parser + +Register your parser during Stac initialization: + +```dart +void main() async { + await Stac.initialize( + options: defaultStacOptions, + parsers: const [ + StacCustomBadgeParser(), + ], + ); + runApp(const MyApp()); +} +``` + +## Advanced Patterns + +### Using Converters + +Stac provides converters for special types. Use them when needed: + +#### DoubleConverter + +For `double` fields that may come as integers in JSON: + +```dart +import 'package:stac_core/core/converters/double_converter.dart'; + +@JsonSerializable() +class StacCustomWidget extends StacWidget { + const StacCustomWidget({ + this.elevation, + }); + + @DoubleConverter() + final double? elevation; + + @override + String get type => 'customWidget'; + + // ... fromJson and toJson +} +``` + +#### StacWidgetConverter + +For child widgets in your custom widget: + +```dart +import 'package:stac_core/core/converters/stac_widget_converter.dart'; + +@JsonSerializable(explicitToJson: true) +class StacCustomContainer extends StacWidget { + const StacCustomContainer({ + this.child, + }); + + @StacWidgetConverter() + final StacWidget? child; + + @override + String get type => 'customContainer'; + + // ... fromJson and toJson +} +``` + +### Using Stac Types + +Prefer Stac types over primitive Dart types for consistency: + +```dart +@JsonSerializable() +class StacCustomWidget extends StacWidget { + const StacCustomWidget({ + this.color, // StacColor, not Color + this.padding, // StacEdgeInsets, not EdgeInsets + this.alignment, // StacAlignment, not Alignment + }); + + final StacColor? color; + final StacEdgeInsets? padding; + final StacAlignment? alignment; + + // ... +} +``` + +## Using Custom Widgets + +**In Dart (stac/ folder)** + +```dart +import 'package:stac_core/stac_core.dart'; +import 'package:your_app/widgets/stac_custom_badge.dart'; + +@StacScreen(screenName: 'profile') +StacWidget profileScreen() { + return StacScaffold( + body: StacColumn( + children: [ + StacCustomBadge( + text: 'New', + color: StacColors.red, + size: 12.0, + ), + StacText(data: 'User Profile'), + ], + ), + ); +} +``` + +After `stac build` or `stac deploy`, your generated json looks like this: + +```json +{ + "type": "scaffold", + "body": { + "type": "column", + "children": [ + { + "type": "customBadge", + "text": "New", + "color": "#FFFF0000", + "size": 12.0 + }, + { + "type": "text", + "data": "User Profile" + } + ] + } +} +``` + diff --git a/docs/concepts/parsers.mdx b/docs/concepts/parsers.mdx deleted file mode 100644 index 04f55788..00000000 --- a/docs/concepts/parsers.mdx +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: "Stac Parsers" -description: "Documentation for Stac Parsers" ---- - -Stac has wide variety of built-in parsers to handle different types of widgets. -However, you can also create custom parsers to handle custom widgets or widgets that are not supported by Stac out of the box. -This guide will walk you through the basics of creating and using parsers in Stac. - -## What is a Stac Parser? - -A [StacParser](https://github.com/StacDev/stac/blob/dev/packages/stac_framework/lib/src/stac_parser.dart) is a custom class that interprets specific JSON objects and converts them into Flutter widgets. -This allows for highly flexible and customizable UI components, tailored to the specific needs of your application. - -## Creating a Custom Widget Parser - -To create a custom parser, you need to follow these steps: - -1. **Define the JSON Structure**: Define the structure of the JSON object that your parser will interpret. This structure should be well-documented and easy to understand. -2. **Create the Parser Class**: Create a new Dart class that extends the `StacParser` class provided by Stac. This class will contain the logic to interpret the JSON object and generate the corresponding Flutter widget. -3. **Register the Parser**: Register the custom parser with Stac so that it can be used to interpret JSON objects. - -## Example Parser - -Below is a step-by-step example of creating a custom parser for a hypothetical CustomButton widget. - -### Step 1: Define the JSON Structure - -The JSON structure for the CustomButton widget might look like this: - -```json -{ - "type": "customButton", - "text": "Click Me", - "color": "#FF5733", - "onPressed": "handleClick" -} -``` - -For this JSON Structure, we can create a data class to represent the CustomButton widget and to provide the fromJson method to convert the JSON object to the CustomButton object. -:::note -Here we are using the freezed package to create the data class. But you can use any other method to create the data class. -::: - -```dart -@freezed -abstract class CustomButton with _$CustomButton { - const factory CustomButton({ - required String text, - required String color, - required VoidCallback onPressed, - }) = _CustomButton; - - factory CustomButton.fromJson(Map json) => - _$CustomButtonFromJson(json); -} -``` - -### Step 2: Create the Parser Class - -Next, we create a new Dart class that extends the `StacParser` class. - -StacParser gives you 3 methods to implement: - -1. `type`: This method should return the type of the widget. The `type` is a unique identifier for the widget that will be used to determine which parser to use. -2. `getModel`: This method extracts and returns the model object from the provided JSON. The model object contains the properties and data required to render the widget. -3. `parse`: This is where you build the Flutter widget using the model object. This method should return the widget that corresponds to the JSON object. - -```dart -class CustomButtonParser extends StacParser { - const CustomButtonParser(); - - @override - String get type => 'customButton'; - - @override - CustomButton getModel(Map json) => CustomButton.fromJson(json); - - @override - Widget parse(BuildContext context, model) { - return ElevatedButton( - onPressed: model.onPressed, - child: Text(model.text), - style: ElevatedButton.styleFrom( - backgroundColor: model.color.toColor, - ), - ); - } -} -``` - - -### Step 3: Register the Parser - -Finally, you need to register the custom parser with Stac so that it can be used to interpret JSON objects. - -There are 2 ways to register a parser: - -1. **Register in `Stac.initialize`**: You can register the parser when initializing Stac by passing it in the `parsers` parameter. - -```dart -void main() async { - await Stac.initialize( - parsers: const [ - CustomButtonParser(), - ], - ); - - runApp(const MyApp()); -} -``` - -When you register the parser in `Stac.initialize`, the parser will be available throughout the app. - -2. **Register through StacRegistry**: You can also register the parser anywhere using the `StacRegistry` class. - -`StacRegistry` provides you with two method to register the widget parser. - -1. Register a single parser - -```dart -StacRegistry.instance.register(CustomButtonParser()); -``` - -2. Register multiple parsers - -```dart -StacRegistry.instance.registerAll([ - StacTextParser(), - StacButtonParser(), -]); -``` - -## Conclusion - -Creating custom parsers in Stac not only allows you to extend the functionality of the library and build highly customizable UI components, but it also enables you to integrate third-party packages into your application. By defining custom parsers for the widgets or components provided by these packages, you can leverage their functionality within the server-driven UI paradigm that Stac offers. This means you can use any Flutter package in your project and render its widgets from a server-side JSON response, further enhancing the flexibility and power of your application. - - diff --git a/docs/docs.json b/docs/docs.json index 64f6326d..f0d21593 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -37,9 +37,8 @@ "icon": "book", "pages": [ "concepts/rendering_stac_widgets", - "concepts/action_parsers", - "concepts/parsers", - "concepts/theming" + "concepts/custom_widgets", + "concepts/custom_actions" ] } ]