supabase / supabase-flutter

Flutter integration for Supabase. This package makes it simple for developers to build secure and scalable products.
https://supabase.com/
MIT License
661 stars 154 forks source link

Realtime return `TIMESTAMP` as `"null"` instead of `null` when creating new items #854

Closed mattermoran closed 3 months ago

mattermoran commented 3 months ago

Describe the bug I have a table with deleted_at TIMESTAMP and when using watchAll the data comes back with deleted_at: null as expected but after adding a new item it's coming back with deleted_at: "null" where null is a string. if I refetch all items when it's back to normal. Seems like the new items that's pushed into the stream upon creation incorrectly handles the nullable timestamp.

To Reproduce Steps to reproduce the behavior:

  1. create a table with nullable timestamp
  2. watch items from that table
  3. create new entry with timestamp set to null
  4. see the timestamp being "null"
  5. refetch the list and see that now it's null

Expected behavior always comes back as null

dshukertjr commented 3 months ago

I couldn't reproduce it. Could you create a minimal reproducible code for the issue?

mattermoran commented 3 months ago

Absolutely. Here's the code:

main.dart ```dart import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; const anonKey = ''; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Supabase.initialize( url: 'http://127.0.0.1:54321', anonKey: anonKey, ); runApp(const MaterialApp(home: App())); } class App extends StatefulWidget { const App({super.key}); @override State createState() => _AppState(); } class _AppState extends State { Stream>>? stream; @override void initState() { super.initState(); stream = Supabase.instance.client.from('bug').stream(primaryKey: ['id']); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ FloatingActionButton( child: const Icon(Icons.refresh), onPressed: () async { setState(() { stream = Supabase.instance.client .from('bug') .stream(primaryKey: ['id']); }); }, ), const SizedBox(width: 8.0), FloatingActionButton( child: const Icon(Icons.add), onPressed: () async { await Supabase.instance.client.from('bug').insert({}); }, ), ], ), body: Column( children: [ StreamBuilder( stream: stream, builder: (context, snapshot) { if (snapshot.hasData) { final data = snapshot.data as List>; return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: data .map( (e) => Text( 'deleted_at == "null"? ${e['deleted_at'] == 'null'}', ), ) .toList(), ); } if (snapshot.hasError) { return Text(snapshot.error.toString()); } return const CircularProgressIndicator(); }, ), ], ), ); } } ```
migration.sql ```sql CREATE TABLE bug ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), deleted_at TIMESTAMP ); ALTER publication supabase_realtime ADD TABLE bug; ```

and here's the video:

video https://github.com/supabase/supabase-flutter/assets/29712386/fabbe9ea-6c1b-4ea1-bc24-dd0268a340e7
dshukertjr commented 3 months ago

@mattermoran Thank you so much for such a detailed and minimal code sample. You truly made my day, I wish all issues were like this.

I can reproduce the error and see the difference between what I was doing. I was testing it with a timestamp with time zone or timestampz type column, not a timestamp column. With timestampz type column, this issue did not happen.

The issue should be resolved on this PR. https://github.com/supabase/supabase-flutter/pull/855

In the mean while, it's not really a workaround if you have a specific reason for using timestamp type, but generally, timestampz type is preferred in many use cases because it takes the time zones into account.

mattermoran commented 3 months ago

thanks for a quick fix @dshukertjr

and thanks for suggestion regading using the timestamptz. I should probably be using that instead :)

have a great day!