我正在使用 Getx 作为状态管理在 flutter 中制作一个新闻应用程序。
流量
如果已有账户 登录屏幕->主屏幕(FeedNewsScreen)
如果是新用户 SingupScreen ->interestScreen ->LoginSreen->MainScreen(FeedNewsScreen)
问题
当我启动应用程序时,它会创建并初始化Authcontroller,然后创建feednewscontroller(我没有调用)然后exploreNewsController(再次没有调用)然后BottomNavigationController
创建新帐户并登录我的帐户后,它显示我没有找到兴趣,但已填充兴趣列表,但从 authcontroller 中的登录方法调用兴趣作为 fetchedIntersts 方法。
3 当从登录屏幕导航后在主屏幕中使用 Get.put(feednewscontroller) 或打开应用程序(如果已登录)时,我认为 feednewscontroller 的 init 不起作用,因为它负责获取新闻
这是我的主文件
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final AuthController authController =
Get.put(AuthController(), permanent: true);
final BottomNavigationController navigationController =
Get.put(BottomNavigationController(), permanent: true);
@override
Widget build(BuildContext context) {
return Obx(() {
if (navigationController.themeMode.value == null ||
authController.user == null) {
return const CircularProgressIndicator();
}
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: AppTheme.lightTheme,
darkTheme: AppTheme.darkTheme,
themeMode: navigationController.themeMode.value,
home: authController.isUserLoggedIn() ? MainPage() : WelcomePage(),
);
});
}
}
这是AuthController
class AuthController extends GetxController {
FirebaseAuth auth = FirebaseAuth.instance;
FirebaseFirestore firestore = FirebaseFirestore.instance;
UserPreference pref = UserPreference();
UserModel user = UserModel();
RxList<String> interests = <String>[].obs;
RxInt maxInterest = 0.obs;
RxBool isPressedSignin = false.obs;
// FeedNewsController feedNewsController = Get.find();
@override
Future<void> onInit() async {
super.onInit();
user = await pref.getUser();
if (user.isLogin == true) {
await fetchInterests();
}
}
Future<bool> register(String email, String password) async {
try {
UserCredential userCredential = await auth.createUserWithEmailAndPassword(
email: email, password: password);
await _setUserToken(userCredential);
return true;
} catch (e) {
Get.snackbar("Error", e.toString(), snackPosition: SnackPosition.BOTTOM);
return false;
}
}
Future<bool> login(String email, String password) async {
bool interestFetched = false;
try {
UserCredential userCredential = await auth.signInWithEmailAndPassword(
email: email, password: password);
user = UserModel(
isLogin: true,
token: await userCredential.user!.getIdToken(),
uid: userCredential.user!.uid);
pref.saveUser(user);
interestFetched = await fetchInterests();
// pref.saveInterest(interests);
// feedNewsController.fetchEveryThingNews();
print("interest fetched $interestFetched");
return interestFetched;
} catch (e) {
Get.snackbar("Error", e.toString(), snackPosition: SnackPosition.BOTTOM);
return interestFetched;
}
}
Future<void> logOut() async {
await auth.signOut();
pref.removeUser();
interests.clear();
}
Future<void> _setUserToken(UserCredential userCredential) async {
User? user = userCredential.user;
if (user != null) {
String? token = await user.getIdToken();
await firestore.collection('users').doc(user.uid).set({
'token': token,
}, SetOptions(merge: true));
}
}
Future<bool> fetchInterests() async {
User? currentUser = auth.currentUser;
if (currentUser != null) {
DocumentSnapshot<Map<String, dynamic>> snapshot =
await firestore.collection('users').doc(currentUser.uid).get();
Map<String, dynamic>? userData = snapshot.data();
if (userData != null && userData['interests'] != null) {
interests.assignAll(List<String>.from(userData['interests']));
print(interests);
return true;
}
}
return false;
}
Future<void> updateInterests() async {
User? currentUser = auth.currentUser;
if (currentUser != null) {
await firestore.collection('users').doc(currentUser.uid).set({
'interests': interests.toList(),
});
}
print('saved $interests');
interests.clear();
}
bool isUserLoggedIn() {
return user.isLogin ?? false;
}
}
这是FeedNewsController
class FeedNewsController extends GetxController {
final NewsRepo _newsRepo = NewsRepo();
UserPreference pref = UserPreference();
AuthController authController = Get.find();
RxInt page = 1.obs;
RxInt pageSize = 10.obs;
RxList<Articles> everyThingNews = <Articles>[].obs;
RxBool isLoading = false.obs;
RxBool isLoadingMore = false.obs;
RxList<String> sortBy = ['relevancy', 'popularity', 'publishedAt'].obs;
RxString currentSort = 'relevancy'.obs;
RxString errorMessage = ''.obs;
@override
Future<void> onInit() async {
super.onInit();
authController.fetchInterests;
print('${authController.interests}');
await fetchEveryThingNews();
}
Future<void> fetchEveryThingNews() async {
print('everything news called');
try {
isLoading(true);
List interests = authController.interests;
print(interests);
// List<String> interests = await pref.getInterest();
if (interests.isEmpty) {
print("$interests no interst found");
errorMessage('no interst found.');
return;
}
String interestsQuery =
interests.map((interest) => '"$interest"').join(' OR ');
NewsModel newsModel = await _newsRepo.fetchEverythingNews(
search: interestsQuery,
page: page.value.toString(),
pageSize: pageSize.value.toString(),
sortBy: currentSort.value,
);
everyThingNews.value = _removeDuplicates(newsModel.articles ?? []);
} catch (e) {
errorMessage(e.toString());
} finally {
isLoading(false);
}
}
Future<void> fetchMoreNews() async {
try {
isLoadingMore(true);
page.value++;
List<String> interests = authController.interests;
String interestsQuery =
interests.map((interest) => '"$interest"').join(' OR ');
NewsModel newsModel = await _newsRepo.fetchEverythingNews(
search: interestsQuery,
page: page.value.toString(),
pageSize: pageSize.value.toString(),
sortBy: currentSort.value,
);
everyThingNews.addAll(_removeDuplicates(newsModel.articles ?? []));
} catch (e) {
errorMessage(e.toString());
} finally {
isLoadingMore(false);
}
}
List<Articles> _removeDuplicates(List<Articles> articles) {
final uniqueArticles = <String>{};
final filteredArticles = <Articles>[];
for (var article in articles) {
if (uniqueArticles.add(article.title!)) {
filteredArticles.add(article);
}
}
return filteredArticles;
}
}
这是singup屏幕
class SignUpScreen extends StatelessWidget {
SignUpScreen({super.key});
final TextEditingController email = TextEditingController();
final TextEditingController password = TextEditingController();
final AuthController authController = Get.find();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(AppDimension().defaultMargin),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 100,
),
Align(
alignment: Alignment.topLeft,
child: Text(
' Get the latest news right away',
style: Theme.of(context).textTheme.titleMedium,
)),
const SizedBox(
height: 40,
),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Email',
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(
height: 8,
),
TextFormField(
controller: email,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
decoration: const InputDecoration(
hintText: "email",
prefixIcon: Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.person),
),
),
),
const SizedBox(
height: 24,
),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Password',
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(
height: 8,
),
TextFormField(
controller: password,
textInputAction: TextInputAction.done,
obscureText: true,
decoration: const InputDecoration(
hintText: "password",
prefixIcon: Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.lock),
),
),
),
const SizedBox(height: 16),
Obx(
() => ElevatedButton(
onPressed: () async {
String xemail = email.text.trim();
String xpassword = password.text.trim();
if (xemail.isEmpty || xpassword.isEmpty) {
print('empty fields;');
} else {
authController.isPressedSignin.value = true;
print(authController.isPressedSignin.value);
bool isSigned =
await authController.register(xemail, xpassword);
if (isSigned) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => InterestsScreen(),
),
);
}
}
},
style: ButtonStyle(
backgroundColor:
const WidgetStatePropertyAll(AppColor.primary),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4)))),
child: authController.isPressedSignin.value
? const CircularProgressIndicator()
: Text(
"sign up".toUpperCase(),
),
),
),
const SizedBox(height: 8),
],
),
),
),
);
}
}
这是兴趣屏幕(用户将在其中选择兴趣)
class InterestsScreen extends StatelessWidget {
InterestsScreen({super.key});
AuthController authController = Get.find();
@override
Widget build(BuildContext context) {
return Obx(
() => Padding(
padding: EdgeInsets.symmetric(vertical: AppDimension().defaultMargin),
child: Column(
children: [
const SizedBox(
height: 24,
),
Expanded(
child: Container(
child: SingleChildScrollView(
child: Wrap(
alignment: WrapAlignment.center,
children: AppString.newsTopics
.map(
(e) => Padding(
padding:
const EdgeInsets.symmetric(horizontal: 4.0),
child: FilledButton(
onPressed: () {
if (authController.interests.contains(e)) {
authController.interests.remove(e);
authController.maxInterest--;
print(
"removed $authController.maxInterest");
} else {
if (authController.maxInterest.value >= 5) {
Get.snackbar("Limit reached",
"You can choose only 5 interests",
snackPosition: SnackPosition.BOTTOM);
} else {
authController.interests.add(e);
authController.maxInterest++;
print(
"added $authController.maxInterest");
}
}
},
style: ButtonStyle(
shape: WidgetStatePropertyAll(
RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(20),
side: const BorderSide(
color: AppColor.light))),
backgroundColor: WidgetStatePropertyAll(
authController.interests.contains(e)
? AppColor.primary
: Colors.transparent)),
child: Text(
e,
style: const TextStyle(color: AppColor.light),
),
),
),
)
.toList(),
),
),
),
),
const SizedBox(
height: 16,
),
Container(
margin: EdgeInsets.symmetric(
horizontal: AppDimension().defaultMargin),
width: double.infinity,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor:
const WidgetStatePropertyAll(AppColor.primary),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4)))),
onPressed: () {
if (authController.interests.isEmpty) {
Get.snackbar("No Interests Selected",
"Please select at least one interest.",
snackPosition: SnackPosition.BOTTOM);
} else {
authController.updateInterests();
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
}
},
child: const Text('Done')),
)
],
),
),
);
}
}
这是登录屏幕
class LoginScreen extends StatelessWidget {
LoginScreen({super.key});
TextEditingController email = TextEditingController();
TextEditingController password = TextEditingController();
AuthController authController = Get.find();
BottomNavigationController navigationController = Get.find();
@override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(AppDimension().defaultMargin),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(
height: 100,
),
Align(
alignment: Alignment.topLeft,
child: Text(
'Lets LogIn',
style: Theme.of(context).textTheme.titleMedium,
),
),
const SizedBox(
height: 40,
),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Email',
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(
height: 8,
),
TextFormField(
controller: email,
keyboardType: TextInputType.emailAddress,
textInputAction: TextInputAction.next,
onSaved: (email) {},
decoration: const InputDecoration(
hintText: "email",
prefixIcon: Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.person),
),
),
),
const SizedBox(
height: 24,
),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Password',
style: Theme.of(context).textTheme.bodyMedium,
),
),
const SizedBox(
height: 8,
),
TextFormField(
controller: password,
textInputAction: TextInputAction.done,
obscureText: true,
decoration: const InputDecoration(
hintText: "password",
prefixIcon: Padding(
padding: EdgeInsets.all(8),
child: Icon(Icons.lock),
),
),
),
const SizedBox(
height: 24,
),
ElevatedButton(
onPressed: () async {
String xemail = email.text.trim();
String xpassword = password.text.trim();
bool isLoggedIn =
await authController.login(xemail, xpassword);
if (isLoggedIn) {
navigationController.currentIndex.value = 0;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => MainPage()),
(Route<dynamic> route) => false,
);
}
},
style: ButtonStyle(
backgroundColor:
const WidgetStatePropertyAll(AppColor.primary),
shape: WidgetStatePropertyAll(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4))),
),
child: Text(
"Login".toUpperCase(),
),
),
const SizedBox(
height: 8,
),
],
),
),
),
);
}
}
这是主屏幕(包含提要屏幕)
class MainPage extends StatelessWidget {
MainPage({super.key});
@override
Widget build(BuildContext context) {
print('main screen called');
final BottomNavigationController navigationController =
Get.find<BottomNavigationController>();
// Get.lazyPut(() => FeedNewsController(), fenix: true);
return Scaffold(
body: Obx(() =>
navigationController.pages[navigationController.currentIndex.value]),
bottomNavigationBar: Obx(
() => BottomNavigationBar(
onTap: (value) => navigationController.onTabTapped(value),
currentIndex: navigationController.currentIndex.value,
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.grid_view_outlined),
label: 'Feed',
),
BottomNavigationBarItem(
icon: Icon(Icons.explore_outlined),
label: 'Explore',
),
BottomNavigationBarItem(
icon: Icon(Icons.person_4_outlined),
label: 'Profile',
)
],
),
),
);
}
}
这是饲料屏幕
class FeedScreen extends StatelessWidget {
final FeedNewsController newsController = Get.find();
final AppDimension dimension = AppDimension();
final ScrollController _scrollController = ScrollController();
FeedScreen({super.key}) {
_scrollController.addListener(_onScroll);
}
void _onScroll() {
if (_scrollController.position.atEdge) {
bool isBottom = _scrollController.position.pixels != 0;
if (isBottom) {
newsController.fetchMoreNews();
}
}
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
elevation: 5,
title: SizedBox(height: 20, child: Image.asset('assets/logo.png')),
),
body: Obx(() {
if (newsController.isLoading.value) {
return const Center(child: AppWidgets.loadingIndicator);
} else if (newsController.errorMessage.isNotEmpty) {
return Center(child: Text('Error: ${newsController.errorMessage}'));
} else if (newsController.everyThingNews.isEmpty) {
return const Center(child: Text('No news articles available.'));
} else {
return SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
const SizedBox(height: 21),
Padding(
padding: EdgeInsets.all(dimension.defaultMargin)
.copyWith(bottom: 0, top: 0),
child: Row(
children: [
Text('My Feed',
style: Theme.of(context).textTheme.titleLarge),
const Spacer(),
const Icon(Icons.search),
],
),
),
const SizedBox(height: 16),
CarouselSlider.builder(
itemCount: 5,
itemBuilder: (context, index, realIndex) {
return NewsCard(
news: newsController.everyThingNews[index]);
},
options: CarouselOptions(
height: 235,
viewportFraction: .8,
enlargeStrategy: CenterPageEnlargeStrategy.scale,
enableInfiniteScroll: false,
initialPage: 2,
autoPlay: true,
),
),
const SizedBox(height: 12),
Padding(
padding: EdgeInsets.all(dimension.defaultMargin)
.copyWith(bottom: 0, top: 0),
child: Row(
children: [
DropdownButton<String>(
value: newsController.currentSort.value,
items: newsController.sortBy.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,
style: Theme.of(context)
.textTheme
.headlineMedium),
);
}).toList(),
onChanged: (newValue) {
newsController.currentSort.value = newValue!;
newsController.page.value = 1;
newsController.fetchEveryThingNews();
},
)
],
),
),
const SizedBox(height: 16),
Padding(
padding: EdgeInsets.all(dimension.defaultMargin)
.copyWith(bottom: 0, top: 0),
child: ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: newsController.everyThingNews.length +
(newsController.isLoadingMore.value ? 1 : 0),
itemBuilder: (context, index) {
if (index < newsController.everyThingNews.length) {
final newsArticle =
newsController.everyThingNews[index + 5];
return Column(
children: [
NewsTile(news: newsArticle),
const Divider(height: 32),
],
);
} else {
return const Center(
child: AppWidgets.loadingIndicator);
}
},
),
),
],
),
);
}
}),
),
);
}
}
我想要
我认为BottomNavigationController中的问题你需要提供来发布BottomNavigationController的代码