The non-abstract class 'AppDatabase' is missing implementations for these members

86 Views Asked by At

Trying to get my HelloWorld app running in Flutter for the first time. I've hit an error that I can't Google my way out of. When running flutter run, I get the following error, and there is nothing listed (no members):

Error (Xcode): lib/database.dart:116:7: 
Error: The non-abstract class 'AppDatabase' is missing implementations for these members:

Here's the code around line 116:

@UseMoor(tables: [Users, Games, Groups, GroupMembers, Leaderboards, UserGameStatus])
116> class AppDatabase extends _$AppDatabase {
  // Define the constructor
  AppDatabase(QueryExecutor e) : super(e);

  // Custom SQL statements for index creation
  Future<void> createIndexes() async {
    await customStatement('CREATE INDEX idx_leaderboard_rank ON leaderboards (rank);');
    await customStatement('CREATE INDEX idx_leaderboard_user_id ON leaderboards (user_id);');
  }
...

All of the tables are defined above this block. I don't know where to go from here. Any help is appreciated. I have run the various clean functions:

flutter clean
flutter pub run build_runner clean
flutter pub run build_runner build
flutter run

And flutter doctor looks good.

The file database.g.dart is getting generated but I don't know what to look for in there.

Here is the code of pubspec.yaml and the complete database.dart file, and main.dart:

pubspec.yaml

name: MyApp
description: HelloWorld

publish_to: 'none' # Remove this line if you wish to publish to pub.dev

version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.1.3"

dependencies:
  flutter:
    sdk: flutter
  moor:
  path_provider:
  path:
  provider:
  sqlite3_flutter_libs:

dev_dependencies:
  build_runner:
  flutter_test:
    sdk: flutter
  google_fonts:
  moor_generator:

flutter:
  uses-material-design: true

main.dart

// main.dart //

import 'package:flutter/material.dart';
import 'package:MyApp/custom_header.dart'; // Import your custom header widget
import 'package:provider/provider.dart';

import 'database.dart'; // Import your database code
import 'homepage.dart';

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => DatabaseProvider(database),
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginPage(),
    );
  }
}

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final TextEditingController usernameController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Login'),
      ),
      body: Center(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // Username field
              TextFormField(
                controller: usernameController,
                decoration: InputDecoration(
                  labelText: 'Username',
                  // You can add other styling or icons here
                ),
              ),
              // Password field
              TextFormField(
                controller: passwordController,
                obscureText: true, // Hide the password
                decoration: InputDecoration(
                  labelText: 'Password',
                  // You can add other styling or icons here
                ),
              ),
              SizedBox(height: 16), // Spacer for separation
              ElevatedButton(
                onPressed: () async {
                  final username = usernameController.text;

                  // Check if the user exists
                  final user = await context.read<DatabaseProvider>().getUser(username);

                  if (user != null) {
                    // User exists, you can proceed with authentication logic
                    // You may also want to navigate to the home page here
                    Navigator.push(
                      context,
                      MaterialPageRoute(
                        builder: (context) => HomePage(user: user)
                      ),
                    );
                  } else {
                    // User does not exist, show an error message or handle as needed
                    showDialog(
                      context: context,
                      builder: (context) {
                        return AlertDialog(
                          title: Text('Login Error'),
                          content: Text('User does not exist.'),
                          actions: [
                            TextButton(
                              onPressed: () {
                                Navigator.of(context).pop();
                              },
                              child: Text('OK'),
                            ),
                          ],
                        );
                      },
                    );
                  }
                },
                child: Text('Login/Register'),
              ),

            ],
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    // Clean up controllers
    usernameController.dispose();
    passwordController.dispose();
    super.dispose();
  }
}

and finally

database.dart

// database.dart //

import 'package:flutter/material.dart' hide Column, Table;
import 'package:moor/moor.dart';

part 'database.g.dart';

final database = AppDatabase(QueryExecutor(databaseFactoryFfi.openDatabase('my.db')));

// Define a DatabaseProvider class to manage database access
class DatabaseProvider extends ChangeNotifier {
  final AppDatabase _database;

  DatabaseProvider(this._database);

  Future<UserWithoutPassword?> getUser(String username) async {
    final query = select(_database.users)..where((user) => user.username.equals(username));
    final userData = await query.getSingleOrNull();

    if (userData != null) {
      return UserWithoutPassword(
        userId: userData.userId,
        username: userData.username,
        email: userData.email,
        createdAt: userData.createdAt,
      );
    } else {
      return null;
    }
  }

  Future<bool> isGameStarted(AppDatabase database) async {
    final now = DateTime.now(); // Get the current date/time

    // Query to check if a game for the current user has started
    final query = select(database.userGameStatus)
      ..where((status) => status.userId.equals(widget.user.userId))
      ..where((status) => status.startDate.isSmallerThanValue(now))
      ..limit(1); // Limit to 1 result

    final result = await query.get();

    // If there is at least one game that has started, return true; otherwise, return false
    return result.isNotEmpty;
  }

  // Add more methods for other database operations here

  // You can also create methods to update data and notify listeners if needed
}

class UserWithoutPassword {
  final int userId;
  final String username;
  final String email;
  final DateTime createdAt;

  UserWithoutPassword({
    required this.userId,
    required this.username,
    required this.email,
    required this.createdAt,
  });
}

// Define tables and columns
@DataClassName('User')
class Users extends Table {
  IntColumn get userId => integer().autoIncrement()();
  TextColumn get username => text().customConstraint('UNIQUE')();
  TextColumn get email => text().customConstraint('UNIQUE')();
  TextColumn get password => text()();
  DateTimeColumn get createdAt => dateTime().customConstraint('DEFAULT NOW() NOT NULL')();
}

class Games extends Table {
  IntColumn get gameId => integer().autoIncrement()();
  DateTimeColumn get startDate => dateTime()();
  DateTimeColumn get endDate => dateTime()();
  BoolColumn get isActive => boolean().customConstraint('DEFAULT true NOT NULL')();
}

class Groups extends Table {
  IntColumn get groupId => integer().autoIncrement()();
  TextColumn get groupName => text()();
  IntColumn get createdByUserId => integer()();
  DateTimeColumn get createdAt => dateTime().customConstraint('DEFAULT NOW() NOT NULL')();
}

class GroupMembers extends Table {
  IntColumn get groupMemberId => integer().autoIncrement()();
  IntColumn get groupId => integer()();
  IntColumn get userId => integer()();
}

@DataClassName('Leaderboard')
class Leaderboards extends Table {
  IntColumn get leaderboardId => integer().autoIncrement()();
  IntColumn get userId => integer().customConstraint('REFERENCES users(user_id)')();
  IntColumn get gameId => integer().customConstraint('REFERENCES games(game_id)')();
  IntColumn get groupId => integer().customConstraint('REFERENCES groups(group_id)')();
  IntColumn get rank => integer()();
}

@DataClassName('UserGameState')
class UserGameStatus extends Table {
  IntColumn get userGameStatusId => integer().autoIncrement()();
  IntColumn get userId => integer()();
  IntColumn get gameId => integer()();
  DateTimeColumn get startDate => dateTime()();
  DateTimeColumn get endDate => dateTime()();
  BoolColumn get isAlive => boolean().customConstraint('DEFAULT true NOT NULL')();
}

@UseMoor(tables: [Users, Games, Groups, GroupMembers, Leaderboards, UserGameStatus])
class AppDatabase extends _$AppDatabase {
  // Define the constructor
  AppDatabase(QueryExecutor e) : super(e);

  // Custom SQL statements for index creation
  Future<void> createIndexes() async {
    await customStatement('CREATE INDEX idx_leaderboard_rank ON leaderboards (rank);');
    await customStatement('CREATE INDEX idx_leaderboard_user_id ON leaderboards (user_id);');
  }

  // Custom SQL statements for trigger creation
  Future<void> createTriggers() async {
    await customStatement('''
      CREATE OR REPLACE FUNCTION update_leaderboard()
      RETURNS TRIGGER AS \$
      BEGIN
        -- Check if a player's is_alive status changes to false
        IF TG_OP = 'UPDATE' AND NEW.is_alive = false THEN
          INSERT INTO leaderboards (user_id, game_id, group_id, wins, rank)
          SELECT
            NEW.user_id,
            NEW.game_id,
            gm.group_id,
            COUNT(*) AS wins,
            RANK() OVER (PARTITION BY NEW.game_id, gm.group_id ORDER BY COUNT(*) DESC) AS rank
          FROM
            user_game_status AS ugs
          INNER JOIN
            group_members AS gm
          ON
            ugs.user_id = gm.user_id
          WHERE
            ugs.game_id = NEW.game_id
            AND ugs.is_alive = false
          GROUP BY
            NEW.user_id, NEW.game_id, gm.group_id;
        END IF;
        RETURN NEW;
      \$ LANGUAGE plpgsql;
    ''');

    await customStatement('''
      CREATE TRIGGER game_completion_trigger
      AFTER UPDATE ON user_game_status
      FOR EACH ROW
      EXECUTE FUNCTION update_leaderboard();
    ''');
  }

}

Also, since I'm new to this, any feedback on structure/style would be helpful but mostly I'm stuck on this one error. Thanks in advance!

1

There are 1 best solutions below

0
Codefarmer On

Ideally, you will want to update the moor package however, the moor has been renamed to drift and the moor package discontinued. Its been advised to to migrate to drift.

Consider switching to drift and updating your code to match then try again.