diff --git a/with_flutter/.env.example b/with_flutter/.env.example new file mode 100644 index 0000000..ec8a8f7 --- /dev/null +++ b/with_flutter/.env.example @@ -0,0 +1,2 @@ +# Postgres URL retrieved here: https://console.neon.tech +POSTGRES_URL="postgresql://neondb_owner:...@ep-...us-east-1.aws.neon.tech/neondb?sslmode=require" \ No newline at end of file diff --git a/with_flutter/.gitignore b/with_flutter/.gitignore new file mode 100644 index 0000000..4e9f6d7 --- /dev/null +++ b/with_flutter/.gitignore @@ -0,0 +1,56 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + + +android/ +ios/ +web/ + + +# Ignore .env and .env.local files +.env +.env.local + +# Allow .env.example to be tracked +!.env.example \ No newline at end of file diff --git a/with_flutter/.metadata b/with_flutter/.metadata new file mode 100644 index 0000000..2d1be89 --- /dev/null +++ b/with_flutter/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: android + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: ios + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: linux + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: macos + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: web + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + - platform: windows + create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/with_flutter/README.md b/with_flutter/README.md new file mode 100644 index 0000000..d74e543 --- /dev/null +++ b/with_flutter/README.md @@ -0,0 +1,43 @@ + + +# Getting started with Neon and Flutter + +## Clone the repository + +```bash +npx degit neondatabase/examples/with-flutter ./with-flutter +``` + +Run the command below to copy the `.env.example` file: + +``` +cp .env.example .env +``` + +## Store your Neon credentials + +Store your Neon credentials in your `.env` file. + +``` +DATABASE_URL="postgresql://neondb_owner:...@ep-...us-east-1.aws.neon.tech/neondb?sslmode=require" +``` + +- `user` is the database user. +- `password` is the database user’s password. +- `endpoint_hostname` is the host with neon.tech as the [TLD](https://www.cloudflare.com/en-gb/learning/dns/top-level-domain/). +- `dbname` is the name of the database. “neondb” is the default database created with each Neon project. +- `?sslmode=require` an optional query parameter that enforces the [SSL](https://www.cloudflare.com/en-gb/learning/ssl/what-is-ssl/) mode while connecting to the Postgres instance for better security. + +**Important**: To ensure the security of your data, never expose your Neon credentials to the browser. + +Run the command below to install project dependencies: + +``` +flutter pub get +``` + +Run the Flutter application using the following command: + +``` +flutter run +``` \ No newline at end of file diff --git a/with_flutter/analysis_options.yaml b/with_flutter/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/with_flutter/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/with_flutter/lib/main.dart b/with_flutter/lib/main.dart new file mode 100644 index 0000000..46616e4 --- /dev/null +++ b/with_flutter/lib/main.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:postgres/postgres.dart'; +import 'package:with_flutter/todos_page.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(fileName: ".env"); + final url = Uri.parse(dotenv.get("DATABASE_URL")); + + final conn = await Connection.open(Endpoint( + host: url.host, + database: url.path.replaceAll("/", ""), + username: url.userInfo.split(':')[0], + password: url.userInfo.split(':')[1], + )); + + runApp(MyApp(db: conn)); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key, required this.db}); + + final Connection db; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter + Neon 🚀', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: TodosPage( + db: db, + ), + ); + } +} diff --git a/with_flutter/lib/todos_page.dart b/with_flutter/lib/todos_page.dart new file mode 100644 index 0000000..0be891c --- /dev/null +++ b/with_flutter/lib/todos_page.dart @@ -0,0 +1,179 @@ +import 'package:flutter/material.dart'; +import 'package:postgres/postgres.dart'; + +class TodosPage extends StatefulWidget { + const TodosPage({super.key, required this.db}); + + final Connection db; + + @override + State createState() => _TodosPageState(); +} + +class _TodosPageState extends State { + List> todos = []; + bool isLoading = true; + String error = ""; + final TextEditingController todoController = TextEditingController(); + + @override + void initState() { + super.initState(); + getTodos(); + } + + void showError(BuildContext context, String error) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Error-$error"), + backgroundColor: Colors.red, + ), + ); + } + + Future createTables() async { + await widget.db.execute( + "CREATE TABLE IF NOT EXISTS todos (id SERIAL, text TEXT, done BOOLEAN DEFAULT false)"); + return; + } + + void getTodos() async { + try { + await createTables(); + final res = await widget.db.execute("select * from todos order by done"); + + todos = res + .map((item) => {"id": item[0], "text": item[1], "done": item[2]}) + .toList(); + isLoading = false; + setState(() {}); + } on Exception catch (e) { + isLoading = false; + error = e.toString(); + showError(context, error); + setState(() {}); + } + } + + void addTodo() async { + try { + final String todo = todoController.text.trim(); + + if (todo.isNotEmpty) { + await widget.db.execute( + r"insert into todos(text) values($1)", + parameters: [todoController.text], + ); + todoController.clear(); + + getTodos(); + } + } on Exception catch (e) { + showError(context, e.toString()); + } + } + + void updateTodo(int id, bool val) async { + try { + await widget.db.execute( + r"update todos set done = $1 where id=$2", + parameters: [val, id], + ); + getTodos(); + } on Exception catch (e) { + showError(context, e.toString()); + } + } + + void deleteTodo(int id) async { + try { + await widget.db.execute( + r"delete from todos where id=$1", + parameters: [id], + ); + getTodos(); + } on Exception catch (e) { + showError(context, e.toString()); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text("Flutter + Neon 🚀"), + ), + body: buildBody(), + ); + } + + Widget buildBody() { + if (isLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else { + return Column( + children: [ + todos.isNotEmpty + ? buildTodoList() + : const Expanded( + child: Center( + child: Text("No Todos"), + ), + ), + buildAddTodoForm(), + ], + ); + } + } + + Widget buildTodoList() { + return Expanded( + child: ListView.builder( + shrinkWrap: true, + itemCount: todos.length, + itemBuilder: (_, i) { + return ListTile( + title: Text(todos[i]["text"]), + leading: Checkbox( + value: todos[i]["done"] ?? false, + onChanged: (val) { + updateTodo(todos[i]['id'], val!); + }, + ), + trailing: IconButton( + onPressed: () => deleteTodo(todos[i]["id"]), + icon: const Icon(Icons.delete), + ), + ); + }, + ), + ); + } + + Widget buildAddTodoForm() { + return Card( + child: Row( + children: [ + // Input Field + Expanded( + child: TextField( + controller: todoController, + decoration: const InputDecoration( + hintText: 'Enter a task...', + border: OutlineInputBorder(), + ), + ), + ), + const SizedBox(width: 8), + // Add Button + ElevatedButton( + onPressed: addTodo, + child: const Text('Add'), + ), + ], + ), + ); + } +} diff --git a/with_flutter/pubspec.lock b/with_flutter/pubspec.lock new file mode 100644 index 0000000..ae5e375 --- /dev/null +++ b/with_flutter/pubspec.lock @@ -0,0 +1,293 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + buffer: + dependency: transitive + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.dev" + source: hosted + version: "1.2.3" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + url: "https://pub.dev" + source: hosted + version: "10.0.5" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" + lints: + dependency: transitive + description: + name: lints + sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" + meta: + dependency: transitive + description: + name: meta + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 + url: "https://pub.dev" + source: hosted + version: "1.15.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + postgres: + dependency: "direct main" + description: + name: postgres + sha256: e9802d4c9d78e432c4d4e57be9a91d99cf1552d45f17b17792f8e595060376a2 + url: "https://pub.dev" + source: hosted + version: "3.4.3" + sasl_scram: + dependency: transitive + description: + name: sasl_scram + sha256: a47207a436eb650f8fdcf54a2e2587b850dc3caef9973ce01f332b07a6fc9cb9 + url: "https://pub.dev" + source: hosted + version: "0.1.1" + saslprep: + dependency: transitive + description: + name: saslprep + sha256: "3d421d10be9513bf4459c17c5e70e7b8bc718c9fc5ad4ba5eb4f5fd27396f740" + url: "https://pub.dev" + source: hosted + version: "1.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + unorm_dart: + dependency: transitive + description: + name: unorm_dart + sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b" + url: "https://pub.dev" + source: hosted + version: "0.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + url: "https://pub.dev" + source: hosted + version: "14.2.5" +sdks: + dart: ">=3.5.3 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/with_flutter/pubspec.yaml b/with_flutter/pubspec.yaml new file mode 100644 index 0000000..6df2c1c --- /dev/null +++ b/with_flutter/pubspec.yaml @@ -0,0 +1,93 @@ +name: with_flutter +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.5.3 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + postgres: ^3.4.3 + flutter_dotenv: ^5.2.1 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^4.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + - .env.local + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/with_flutter/test/widget_test.dart b/with_flutter/test/widget_test.dart new file mode 100644 index 0000000..2a2b819 --- /dev/null +++ b/with_flutter/test/widget_test.dart @@ -0,0 +1,8 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +void main() {}