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
738 stars 184 forks source link

Getting "FormatException (FormatException: Unexpected end of input (at character 1)" when inserting to database #572

Closed DeniseWongWaiYan closed 8 months ago

DeniseWongWaiYan commented 1 year ago

Describe the bug I am getting the FormatException (FormatException: Unexpected end of input (at character 1) error when inserting to Supabase.

To Reproduce In my app, I have this function.

  1. User clicks button to trigger _submitMessage()

    void _submitMessage () async {
    
    setState(() {
      _loading = true;
    });
    
    final shout = _textController.text.trim();
    final profiles_id = ref.watch(activeProfileProvider)?.valueOrNull?.profilesId;
    
    final message = {
        'content': shout,
        'profile_id': profiles_id,
    };
    
    if (shout.isEmpty) {
      return;
    }  
    _textController.clear();
    
    try {
      await supabase.from('quad_shouts').insert(message);
    } on PostgrestException catch (error) {
      SnackBar(
        content: Text(error.message),
        backgroundColor: Theme.of(context).colorScheme.error,
      );
    } catch (error) {
      const SnackBar(
        content: Text('Unexpected error occurred'),
        backgroundColor: Colors.black,
      );
    }  finally {
      if (mounted) {
        setState(() {
          _loading = false;
          print('error');
        });
      }
    }
    }
  2. I get a Getting "FormatException (FormatException: Unexpected end of input (at character 1)" at convert_patch.dart, which seems to be downstream from an error from postgrest_builder.dart from postgrest-1.4.0

  3. As I had modelled the above from this tutorial https://supabase.com/blog/flutter-tutorial-building-a-chat-app#step-7-create-a-chat-page-to-receive-and-send-real-time-messages, I followed this tutorial to create a messages table, and copied the code here for Step 7: Create a chat page to receive and send real time messages. I get the same error.

Version (please complete the following information): ├── postgrest 1.4.0 ├── supabase_flutter 1.10.10 │ ├── supabase 1.9.8 │ │ ├── functions_client 1.3.2 │ │ ├── gotrue 1.11.1 │ │ ├── postgrest... │ │ ├── realtime_client 1.1.2 │ │ ├── storage_client 1.5.1

I'm a bit lost. Would really appreciate any help. Thanks!

DeniseWongWaiYan commented 1 year ago

I'm getting a new error, without having changed any code.

PostgrestException (PostgrestException(message: invalid input syntax for type uuid: "null", code: 22P02, details: Bad Request, hint: null))

Are these all related - https://github.com/orgs/supabase/discussions/1254?

Vinzent03 commented 1 year ago

Please share the exact stacktrace of your first error. I guess your second error is caused by profile_id being null here.

DeniseWongWaiYan commented 1 year ago

I think I've sorted the second error.

For the first one, is this the stacktrace?

_ChunkedJsonParser.fail (dart:convert-patch/convert_patch.dart:1383)
_ChunkedJsonParser.close (dart:convert-patch/convert_patch.dart:501)
_parseJson (dart:convert-patch/convert_patch.dart:36)
JsonDecoder.convert (dart:convert/json.dart:610)
JsonCodec.decode (dart:convert/json.dart:216)
jsonDecode (dart:convert/json.dart:155)
PostgrestBuilder._parseResponse (/Users/denisewong/.pub-cache/hosted/pub.dev/postgrest-1.4.0/lib/src/postgrest_builder.dart:222)
PostgrestBuilder._execute (/Users/denisewong/.pub-cache/hosted/pub.dev/postgrest-1.4.0/lib/src/postgrest_builder.dart:202)
<asynchronous gap> (Unknown Source:0)
PostgrestBuilder.then (/Users/denisewong/.pub-cache/hosted/pub.dev/postgrest-1.4.0/lib/src/postgrest_builder.dart:422)
<asynchronous gap> (Unknown Source:0)

Thanks again for your help!

Vinzent03 commented 1 year ago

Are you actually getting that error in the console? Any `FormatException' gets caught there. So I guess you only get that when running the debugger and stop at every exception, even though it gets caught.

dshukertjr commented 1 year ago

@DeniseWongWaiYan Not sure about the original issue, but this error happening, because you passed "null" as a value for a column with type UUID. Make sure you are passing a value that matches the format of the column and the error should go away.

DeniseWongWaiYan commented 1 year ago

Yeah, I stop at every exception instead of the console. @dshukertjr thanks, but none of the UUID columns are null and it's not nullable anyway.

The weird thing is, it's actually writing to Supabase successfully, but the FormatException still occurs.

Thanks.

DeniseWongWaiYan commented 1 year ago

This error basically happens whenever I try to INSERT to Supabase and now I'm getting this new error:

PostgrestException (PostgrestException(message: JSON object requested, multiple (or no) rows returned, code: PGRST116, details: Results contain 0 rows, application/vnd.pgrst.object+json requires 1 row, hint: null))

dshukertjr commented 1 year ago

@DeniseWongWaiYan Could you share the table definition of the table you are trying to perform insert/ upsert on (preferably in SQL), and the exact data that you are inserting/ upserting into the table in Dart Map format?

DeniseWongWaiYan commented 1 year ago

It's happening to all the tables, but here are two of the tables.

create table
  public.profiles (
    auth_ids uuid not null default gen_random_uuid (),
    updated_at timestamp with time zone null,
    username text null,
    full_name text null,
    avatar_url text null,
    profile_id uuid not null default gen_random_uuid (),
    active boolean not null default false,
    constraint profiles_pkey primary key (profile_id),
    constraint profiles_profiles_id_key unique (profile_id),
    constraint profiles_username_key unique (username),
    constraint profiles_auth_ids_fkey foreign key (auth_ids) references auth.users (id) on delete cascade,
    constraint username_length check ((char_length(username) >= 3))
  ) tablespace pg_default;

I'm trying it now with hard coded variables.

final updates = {
      'username': 'testquad2',
      'full_name': 'testquad', 
      'updated_at': DateTime.now().toIso8601String(),
      'auth_ids' : user!.id,
    };

Previously, using this

Future<void> _updateoraddProfile() async {
  setState(() {
      _loading = true;
    });

    final arguments = (ModalRoute.of(context)?.settings.arguments ?? <String, dynamic>{}) as Map;
    final selectedProfileId = arguments['selectedprofile'];

    final userName = _usernameController.text.trim();
    final fullName = _fullnameController.text;

    final updates = {
      'username': userName,
      'full_name': fullName, 
      'updated_at': DateTime.now().toIso8601String(),
      'auth_ids' : user!.id,
    };

  try {
    if (selectedProfileId != null){
      await supabase.from('profiles').update(updates).eq('profile_id', selectedProfileId).select();
    } else {
      await supabase.from('profiles').upsert(updates);
    }

    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Profile Saved!')),);
      Navigator.of(context).pushReplacementNamed(ProfileListPage.id);
      }
  } on PostgrestException catch (error) {
          SnackBar(
        content: Text(error.message),
        backgroundColor: Theme.of(context).colorScheme.error,
      );
  } catch (error) {
          SnackBar(
        content: const Text('Unexpected error occurred'),
        backgroundColor: Theme.of(context).colorScheme.error,
      );
  } finally {
      if (mounted) {
        setState(() {
          _loading = false;
        });
      }
    }
  }
  Widget _buildWidget(BuildContext context) {

  return Scaffold(
    appBar: const MainAppBar(),
    body: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: 
            <Widget>[
              Text(user?.email ?? 'Not logged in'),
               const SizedBox(
                height: 48.0,
              ),
              TextField(
                  controller: _usernameController,
                  decoration: const InputDecoration(labelText: 'User Name'),
              ),
              const SizedBox(
                height: 18.0,
              ),
              TextField(
                controller: _fullnameController,
                  decoration: const InputDecoration(labelText: 'Full Name'),
              ),
              const SizedBox(
                height: 18.0,
              ),
              ElevatedButton(
                  onPressed: _loading ? null : _updateoraddProfile,
                  child: Text(_loading ? 'Saving...' : 'Update'),
                ),

            ],
          ),
        ),
  );

}

Models

class Profile {
  Profile({
    this.profilesId,
    this.authid,
    this.updated_at,
    this.username,
    this.fullname,
    this.avatar_url,
    this.active, 
  });

  final String? profilesId;
  final String? authid;
  final DateTime? updated_at;
  final String? username;
  final String? fullname;
  final String? avatar_url;
  final bool? active;

  Profile.fromMap({
    required Map<String, dynamic> map,
  })  : authid = map['auth_ids'],
        profilesId = map['profile_id'],
        username = map['username'],
        fullname = map['full_name'],
        avatar_url = map['avatar_url'],
        active = map['active'],
        updated_at = DateTime.parse(map['updated_at']);

}

create table
  public.quad_shouts (
    created_at timestamp with time zone null default now(),
    profile_id uuid not null,
    content text null,
    shout_id uuid not null default gen_random_uuid (),
    constraint quad_shouts_pkey primary key (shout_id)
  ) tablespace pg_default;

I've tried this

final profilesId = ref.watch(activeProfileProvider).valueOrNull?.profilesId;

    final message = {
        'content': 'shout',
        'profile_id': profilesId,
    };

I've also tried

_submitMessage () async {

    setState(() {
      _loading = true;
    });

    final shout = _textController.text;
    final profilesId = ref.watch(activeProfileProvider).valueOrNull?.profilesId;

    final message = {
        'content': shout,
        'profile_id': profilesId,
    };

    if (shout.isEmpty) {
      return;
    }  
    _textController.clear();

    try {
      await supabase.from('quad_shouts').insert(message);

    } on PostgrestException catch (error) {
      SnackBar(
        content: Text(error.message),
        backgroundColor: Theme.of(context).colorScheme.error,
      );
    } catch (error) {
      const SnackBar(
        content: Text('Unexpected error occurred'),
        backgroundColor: Colors.black,
      );
    }  finally {
      if (mounted) {
        setState(() {
          _loading = false;
        });
      }
    }
  }

  }
Widget build(BuildContext context) {
     final shouts = ref.watch(shoutProvider);

       if (user?.id == null) {
          Future.delayed(Duration.zero, () {Navigator.of(context).pushReplacementNamed(SignupPage.id);});
      }

    return shouts.when(
      error: (error, stackTrace) => Text(error.toString()), 
      loading: () => const CircularProgressIndicator(),
      data: (shout) {
        return Scaffold(
          body: Column(
            children: [
              Expanded(
                child: 
                ListView.builder(
                  reverse: true,
                  shrinkWrap:true,
                  itemCount: shout.length,
                  itemBuilder: (((context, index) {
                    return Container(margin: const EdgeInsets.only(right:40, left: 10), child: PaqaMessage(profile:shout[index].profileId ?? 'null', content: shout[index].content ?? 'no text'));
                    // return Text(shout[index].content);
                  }))
                ),
              ),
              SafeArea(
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Row(
                    children: [
                      MessageBar(textController: _textController),
                      TextButton(
                        style: TextButton.styleFrom(backgroundColor: Colors.pink),
                          onPressed: () => _submitMessage(),
                          child: Text(_loading ? 'Shouting' : 'Shout, ${ref.watch(activeProfileProvider).valueOrNull?.fullname}'),
                        ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        );
      }, 
    );
  }
class Shout {
  Shout({
    this.id,
    this.profileId,
    this.content,
    this.createdAt,

  });

  /// ID of the message
  final String? id;

  /// ID of the user who posted the message
  final String? profileId;

  /// Text content of the message
  final String? content;

  /// Date and time when the message was created
  final DateTime? createdAt;

  Shout.fromMap({
    required Map<String, dynamic> map,
  }) : id = map['shout_id'],
        profileId = map['profile_id'],
        content = map['content'],
        createdAt = DateTime.parse(map['created_at']);
}

I've already checked for any constraints for NULL or UNIQUE values.

Thanks again, all for your ongoing help. I really, really appreciate it. Thanks!

Vinzent03 commented 1 year ago

That's a bit much of code. Which code exactly still fails? Best without any Flutter code and just Dart. So a simple supabase postgrest query with constant data.

Vinzent03 commented 8 months ago

I'm closing this because of no response and the fact I couldn't reproduce this. Additionally, a lot has changed since then. Please open a new issue with a more concise, reproducible example if you encounter something similar.