|
2 | 2 | <a href="https://dscvit.com"> |
3 | 3 | <img width="400" src="https://user-images.githubusercontent.com/56252312/159312411-58410727-3933-4224-b43e-4e9b627838a3.png#gh-light-mode-only" alt="GDSC VIT"/> |
4 | 4 | </a> |
5 | | - <h2 align="center"> < Insert Project Title Here > </h2> |
6 | | - <h4 align="center"> < Insert Project Description Here > <h4> |
| 5 | + <h2 align="center">Flutter SDUI Package</h2> |
| 6 | + <h4 align="center">A Flutter package for implementing Server-Driven UI with both JSON and gRPC support<h4> |
7 | 7 | </p> |
8 | 8 |
|
9 | | ---- |
10 | 9 | [](https://dsc.community.dev/vellore-institute-of-technology/) |
11 | 10 | [](https://discord.gg/498KVdSKWR) |
| 11 | +[](docs/grpc_support.md) |
| 12 | +[](https://flutter.dev) |
12 | 13 |
|
13 | | -[](INSERT_LINK_FOR_DOCS_HERE) |
14 | | - [](INSERT_UI_LINK_HERE) |
| 14 | +A powerful Flutter package for implementing Server-Driven UI (SDUI) with both JSON and gRPC support. |
15 | 15 |
|
| 16 | +## What is SDUI? |
| 17 | + |
| 18 | +Server-Driven UI is an architectural pattern where the UI layout and content definitions come from a backend server rather than being hardcoded in the client application. This approach enables: |
| 19 | + |
| 20 | +- Dynamic UI updates without app store releases |
| 21 | +- A/B testing and feature flagging at the UI level |
| 22 | +- Consistent UI across platforms |
| 23 | +- Faster iteration cycles for UI changes |
16 | 24 |
|
17 | 25 | ## Features |
18 | | -- [ ] < feature > |
19 | | -- [ ] < feature > |
20 | | -- [ ] < feature > |
21 | | -- [ ] < feature > |
22 | 26 |
|
23 | | -<br> |
| 27 | +- [x] Render UI dynamically from server-provided definitions |
| 28 | +- [x] Support for essential Flutter widgets (Text, Column, Row, Container, etc.) |
| 29 | +- [x] JSON parsing for server responses |
| 30 | +- [x] gRPC support for efficient, type-safe communication |
| 31 | +- [x] Protocol Buffers for structured data exchange |
| 32 | +- [x] Easy-to-use client API |
| 33 | +- [x] Customizable error handling and loading states |
24 | 34 |
|
25 | | -## Dependencies |
26 | | - - < dependency > |
27 | | - - < dependency > |
| 35 | +## Installation |
28 | 36 |
|
| 37 | +Add the package to your `pubspec.yaml`: |
| 38 | + |
| 39 | +```yaml |
| 40 | +dependencies: |
| 41 | + flutter_sdui: ^0.0.1 |
| 42 | +``` |
29 | 43 |
|
30 | | -## Running |
| 44 | +```yaml |
| 45 | +# For devs |
| 46 | +dependencies: |
| 47 | + flutter_sdui: |
| 48 | + path: path/to/flutter_sdui |
| 49 | +``` |
31 | 50 |
|
| 51 | +Or use the Flutter CLI: |
32 | 52 |
|
33 | | -< directions to install > |
34 | 53 | ```bash |
35 | | -< insert code > |
| 54 | +flutter pub add flutter_sdui |
| 55 | +``` |
| 56 | + |
| 57 | +Import the package in your Dart code: |
| 58 | + |
| 59 | +```dart |
| 60 | +import 'package:flutter_sdui/flutter_sdui.dart'; |
| 61 | +``` |
| 62 | + |
| 63 | +## Basic Usage |
| 64 | + |
| 65 | +This package provides two approaches for implementing server-driven UI: |
| 66 | + |
| 67 | +### 1. Using gRPC (Recommended) |
| 68 | + |
| 69 | +For efficient, type-safe server communication: |
| 70 | + |
| 71 | +```dart |
| 72 | +// Create a gRPC client |
| 73 | +final client = SduiGrpcClient( |
| 74 | + host: 'your-server.com', |
| 75 | + port: 50051, |
| 76 | +); |
| 77 | +
|
| 78 | +// Use the SduiGrpcRenderer widget |
| 79 | +SduiGrpcRenderer( |
| 80 | + client: client, |
| 81 | + screenId: 'home_screen', |
| 82 | + loadingWidget: CircularProgressIndicator(), |
| 83 | + errorBuilder: (context, error) => Text('Error: $error'), |
| 84 | +) |
| 85 | +``` |
| 86 | + |
| 87 | +### 2. Using JSON |
| 88 | + |
| 89 | +For simpler implementation with standard HTTP requests (coming soon). |
| 90 | + |
| 91 | +## Example |
| 92 | + |
| 93 | +Here's a complete example of using the gRPC renderer: |
| 94 | + |
| 95 | +```dart |
| 96 | +import 'package:flutter/material.dart'; |
| 97 | +import 'package:flutter_sdui/flutter_sdui.dart'; |
| 98 | +
|
| 99 | +void main() { |
| 100 | + runApp(const MyApp()); |
| 101 | +} |
| 102 | +
|
| 103 | +class MyApp extends StatelessWidget { |
| 104 | + const MyApp({super.key}); |
| 105 | +
|
| 106 | + @override |
| 107 | + Widget build(BuildContext context) { |
| 108 | + return MaterialApp( |
| 109 | + title: 'SDUI Demo', |
| 110 | + theme: ThemeData( |
| 111 | + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), |
| 112 | + useMaterial3: true, |
| 113 | + ), |
| 114 | + home: const SDUIDemo(), |
| 115 | + ); |
| 116 | + } |
| 117 | +} |
| 118 | +
|
| 119 | +class SDUIDemo extends StatefulWidget { |
| 120 | + const SDUIDemo({super.key}); |
| 121 | +
|
| 122 | + @override |
| 123 | + State<SDUIDemo> createState() => _SDUIDemoState(); |
| 124 | +} |
| 125 | +
|
| 126 | +class _SDUIDemoState extends State<SDUIDemo> { |
| 127 | + late SduiGrpcClient _grpcClient; |
| 128 | + String _screenId = 'home'; |
| 129 | +
|
| 130 | + @override |
| 131 | + void initState() { |
| 132 | + super.initState(); |
| 133 | + _grpcClient = SduiGrpcClient( |
| 134 | + host: 'localhost', // Replace with your server address |
| 135 | + port: 50051, // Replace with your server port |
| 136 | + ); |
| 137 | + } |
| 138 | +
|
| 139 | + @override |
| 140 | + void dispose() { |
| 141 | + _grpcClient.dispose(); |
| 142 | + super.dispose(); |
| 143 | + } |
| 144 | +
|
| 145 | + @override |
| 146 | + Widget build(BuildContext context) { |
| 147 | + return Scaffold( |
| 148 | + appBar: AppBar( |
| 149 | + title: const Text('Server-Driven UI Demo'), |
| 150 | + ), |
| 151 | + body: SduiGrpcRenderer( |
| 152 | + client: _grpcClient, |
| 153 | + screenId: _screenId, |
| 154 | + loadingWidget: const Center( |
| 155 | + child: CircularProgressIndicator(), |
| 156 | + ), |
| 157 | + errorBuilder: (context, error) { |
| 158 | + return Center( |
| 159 | + child: Column( |
| 160 | + mainAxisAlignment: MainAxisAlignment.center, |
| 161 | + children: [ |
| 162 | + Icon(Icons.error, color: Colors.red, size: 48), |
| 163 | + SizedBox(height: 16), |
| 164 | + Text('Error: $error'), |
| 165 | + SizedBox(height: 16), |
| 166 | + ElevatedButton( |
| 167 | + onPressed: () => setState(() {}), |
| 168 | + child: Text('Retry'), |
| 169 | + ), |
| 170 | + ], |
| 171 | + ), |
| 172 | + ); |
| 173 | + }, |
| 174 | + ), |
| 175 | + ); |
| 176 | + } |
| 177 | +} |
36 | 178 | ``` |
37 | 179 |
|
38 | | -< directions to execute > |
| 180 | +## Server Implementation |
| 181 | + |
| 182 | +### Setting Up a gRPC Server |
| 183 | + |
| 184 | +Here's a basic example of a Dart server that provides UI definitions via gRPC: |
| 185 | + |
| 186 | +```dart |
| 187 | +import 'package:grpc/grpc.dart'; |
| 188 | +import 'package:flutter_sdui/src/generated/sdui.pb.dart'; |
| 189 | +import 'package:flutter_sdui/src/generated/sdui.pbgrpc.dart'; |
| 190 | +
|
| 191 | +Future<void> main() async { |
| 192 | + final server = Server.create( |
| 193 | + services: [ |
| 194 | + SduiServiceImpl(), |
| 195 | + ], |
| 196 | + ); |
| 197 | +
|
| 198 | + await server.serve(port: 50051); |
| 199 | + print('Server listening on port 50051...'); |
| 200 | +} |
| 201 | +
|
| 202 | +class SduiServiceImpl extends SduiServiceBase { |
| 203 | + @override |
| 204 | + Future<SduiWidgetData> getSduiWidget( |
| 205 | + ServiceCall call, SduiRequest request) async { |
| 206 | + // Return different UI based on the screenId |
| 207 | + switch (request.screenId) { |
| 208 | + case 'home': |
| 209 | + return _createHomeScreen(); |
| 210 | + default: |
| 211 | + return _createErrorScreen(); |
| 212 | + } |
| 213 | + } |
| 214 | +
|
| 215 | + SduiWidgetData _createHomeScreen() { |
| 216 | + return SduiWidgetData() |
| 217 | + ..type = WidgetType.SCAFFOLD |
| 218 | + ..body = (SduiWidgetData() |
| 219 | + ..type = WidgetType.COLUMN |
| 220 | + ..children.addAll([ |
| 221 | + SduiWidgetData() |
| 222 | + ..type = WidgetType.CONTAINER |
| 223 | + ..padding = (EdgeInsetsData()..all = 16) |
| 224 | + ..child = (SduiWidgetData() |
| 225 | + ..type = WidgetType.TEXT |
| 226 | + ..stringAttributes['text'] = 'Welcome to Server-Driven UI!' |
| 227 | + ..textStyle = (TextStyleData() |
| 228 | + ..fontSize = 22 |
| 229 | + ..fontWeight = 'bold')), |
| 230 | + SduiWidgetData() |
| 231 | + ..type = WidgetType.TEXT |
| 232 | + ..stringAttributes['text'] = 'This UI is rendered from gRPC data' |
| 233 | + ])); |
| 234 | + } |
| 235 | +} |
| 236 | +``` |
| 237 | + |
| 238 | +## Supported Widgets |
| 239 | + |
| 240 | +The package currently supports these Flutter widgets: |
| 241 | + |
| 242 | +- `Scaffold` |
| 243 | +- `Container` |
| 244 | +- `Column` |
| 245 | +- `Row` |
| 246 | +- `Text` |
| 247 | +- `Image` |
| 248 | +- `SizedBox` |
| 249 | +- `Spacer` |
| 250 | +- `Icon` |
| 251 | + |
| 252 | +## Advanced Usage |
| 253 | + |
| 254 | +### Protobuf Definitions |
| 255 | + |
| 256 | +The package uses Protocol Buffers to define the data structures for gRPC communication. Here's a simplified version of the main message types: |
| 257 | + |
| 258 | +```protobuf |
| 259 | +message SduiWidgetData { |
| 260 | + WidgetType type = 1; |
| 261 | + map<string, string> string_attributes = 2; |
| 262 | + map<string, double> double_attributes = 3; |
| 263 | + map<string, bool> bool_attributes = 4; |
| 264 | +
|
| 265 | + // Complex nested attributes |
| 266 | + TextStyleData text_style = 6; |
| 267 | + EdgeInsetsData padding = 7; |
| 268 | +
|
| 269 | + // Children widgets |
| 270 | + repeated SduiWidgetData children = 12; |
| 271 | + SduiWidgetData child = 13; |
| 272 | +
|
| 273 | + // Scaffold specific parts |
| 274 | + SduiWidgetData app_bar = 14; |
| 275 | + SduiWidgetData body = 15; |
| 276 | +} |
| 277 | +
|
| 278 | +service SduiService { |
| 279 | + rpc GetSduiWidget (SduiRequest) returns (SduiWidgetData); |
| 280 | +} |
| 281 | +``` |
| 282 | + |
| 283 | +### Working with Protocol Buffers |
| 284 | + |
| 285 | +If you need to regenerate the Dart files from the proto definitions: |
| 286 | + |
| 287 | +1. Install the Protocol Buffer compiler using the provided scripts: |
39 | 288 |
|
40 | 289 | ```bash |
41 | | -< insert code > |
| 290 | +# Windows |
| 291 | +pwsh ./tool/setup_protoc.ps1 |
| 292 | + |
| 293 | +# Generate the Protobuf files |
| 294 | +pwsh ./tool/generate_protos.ps1 |
42 | 295 | ``` |
43 | 296 |
|
| 297 | +## Roadmap |
| 298 | + |
| 299 | +- [x] Basic widget support |
| 300 | +- [x] gRPC implementation |
| 301 | +- [ ] JSON implementation |
| 302 | +- [ ] Interactive widgets (buttons, forms) |
| 303 | +- [ ] More advanced widget support |
| 304 | + |
| 305 | +## Contributing |
| 306 | + |
| 307 | +We welcome contributions! Please see our [contributing guidelines](contributing.md) for details. |
| 308 | + |
| 309 | +## License |
| 310 | + |
| 311 | +This project is licensed under the [LICENSE](LICENSE) file in the repository. |
| 312 | + |
44 | 313 | ## Contributors |
45 | 314 |
|
46 | 315 | <table> |
47 | 316 | <tr align="center"> |
48 | 317 | <td> |
49 | | - John Doe |
| 318 | + Jothish Kamal |
50 | 319 | <p align="center"> |
51 | | - <img src = "https://dscvit.com/images/dsc-logo-square.svg" width="150" height="150" alt="Your Name Here (Insert Your Image Link In Src"> |
| 320 | + <img src = "https://avatars.githubusercontent.com/u/74227363?v=4" width="150" height="150" alt="Jothish Kamal"> |
52 | 321 | </p> |
53 | 322 | <p align="center"> |
54 | | - <a href = "https://github.com/person1"> |
| 323 | + <a href = "https://github.com/JothishKamal"> |
55 | 324 | <img src = "http://www.iconninja.com/files/241/825/211/round-collaboration-social-github-code-circle-network-icon.svg" width="36" height = "36" alt="GitHub"/> |
56 | 325 | </a> |
57 | | - <a href = "https://www.linkedin.com/in/person1"> |
| 326 | + <a href = "https://www.linkedin.com/in/jothishkamal"> |
58 | 327 | <img src = "http://www.iconninja.com/files/863/607/751/network-linkedin-social-connection-circular-circle-media-icon.svg" width="36" height="36" alt="LinkedIn"/> |
59 | 328 | </a> |
60 | 329 | </p> |
|
0 commit comments