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!
Ideally, you will want to update the
moorpackage however, themoorhas been renamed to drift and themoorpackage discontinued. Its been advised to to migrate todrift.Consider switching to
driftand updating your code to match then try again.