Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add connectivity management with connectivity_plus package #2325

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mobile-v3/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:label="airqo"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
public final class GeneratedPluginRegistrant {
private static final String TAG = "GeneratedPluginRegistrant";
public static void registerWith(@NonNull FlutterEngine flutterEngine) {
try {
flutterEngine.getPlugins().add(new dev.fluttercommunity.plus.connectivity.ConnectivityPlugin());
} catch (Exception e) {
Log.e(TAG, "Error registering plugin connectivity_plus, dev.fluttercommunity.plus.connectivity.ConnectivityPlugin", e);
}
try {
flutterEngine.getPlugins().add(new io.flutter.plugins.flutter_plugin_android_lifecycle.FlutterAndroidLifecyclePlugin());
} catch (Exception e) {
Expand Down
7 changes: 7 additions & 0 deletions mobile-v3/ios/Runner/GeneratedPluginRegistrant.m
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@

#import "GeneratedPluginRegistrant.h"

#if __has_include(<connectivity_plus/ConnectivityPlusPlugin.h>)
#import <connectivity_plus/ConnectivityPlusPlugin.h>
#else
@import connectivity_plus;
#endif

Comment on lines +9 to +14
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid manual modifications to generated files

GeneratedPluginRegistrant.m is an auto-generated file. Manual edits might be overwritten. Ensure that the connectivity_plus plugin is correctly added to your pubspec.yaml, and let the Flutter tooling handle the plugin registration.

To include the plugin properly, verify that you've added it to pubspec.yaml:

dependencies:
  connectivity_plus: ^6.1.0

Then, run flutter pub get and rebuild the project to auto-generate the correct registrant files.

Also applies to: 30-30

#if __has_include(<google_maps_flutter_ios/FLTGoogleMapsPlugin.h>)
#import <google_maps_flutter_ios/FLTGoogleMapsPlugin.h>
#else
Expand All @@ -21,6 +27,7 @@
@implementation GeneratedPluginRegistrant

+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
[ConnectivityPlusPlugin registerWithRegistrar:[registry registrarForPlugin:@"ConnectivityPlusPlugin"]];
[FLTGoogleMapsPlugin registerWithRegistrar:[registry registrarForPlugin:@"FLTGoogleMapsPlugin"]];
[PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]];
}
Expand Down
59 changes: 45 additions & 14 deletions mobile-v3/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@ import 'package:airqo/src/app/other/theme/bloc/theme_bloc.dart';
import 'package:airqo/src/app/other/theme/repository/theme_repository.dart';
import 'package:airqo/src/app/profile/bloc/user_bloc.dart';
import 'package:airqo/src/app/profile/repository/user_repository.dart';
import 'package:airqo/src/app/shared/bloc/connectivity_bloc.dart';
import 'package:airqo/src/app/shared/pages/nav_page.dart';

import 'package:airqo/src/meta/utils/colors.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
import 'package:airqo/src/app/shared/pages/no_internet_banner.dart';

import 'src/app/shared/repository/hive_repository.dart';

Expand Down Expand Up @@ -67,6 +70,7 @@ class AirqoMobile extends StatelessWidget {

@override
Widget build(BuildContext context) {
final connectivity = Connectivity();
return MultiBlocProvider(
providers: [
BlocProvider(
Expand All @@ -93,7 +97,10 @@ class AirqoMobile extends StatelessWidget {
),
BlocProvider(
create: (context) => MapBloc(mapRepository)..add(LoadMap()),
)
),
BlocProvider(
create: (context) => ConnectivityBloc(connectivity),
),
],
child: BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, state) {
Expand Down Expand Up @@ -131,18 +138,42 @@ class Decider extends StatefulWidget {
class _DeciderState extends State<Decider> {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: HiveRepository.getData('token', HiveBoxNames.authBox),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (!snapshot.hasData) {
return WelcomeScreen();
} else {
return NavPage();
}
} else {
return Scaffold(body: Center(child: Text('An Error occured.')));
}
});
return BlocBuilder<ConnectivityBloc, ConnectivityState>(
builder: (context, connectivityState) {
debugPrint('Current connectivity state: $connectivityState');
return Stack(
children: [
FutureBuilder(
future: HiveRepository.getData('token', HiveBoxNames.authBox),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (!snapshot.hasData) {
return WelcomeScreen();
} else {
return NavPage();
}
} else {
return Scaffold(
body: Center(child: Text('An Error occurred.')));
}
},
),
if (connectivityState is ConnectivityOffline)
Positioned(
top: 0,
left: 0,
right: 0,
child: NoInternetBanner(
onClose: () {
context
.read<ConnectivityBloc>()
.add(ConnectivityBannerDismissed());
},
),
),
],
);
},
);
}
}
79 changes: 79 additions & 0 deletions mobile-v3/lib/src/app/shared/bloc/connectivity_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'dart:io' show InternetAddress;

part 'connectivity_event.dart';
part 'connectivity_state.dart';

class ConnectivityBloc extends Bloc<ConnectivityEvent, ConnectivityState> {
final Connectivity _connectivity;
StreamSubscription? _connectivitySubscription;
bool _bannerDismissed = false;
bool get isBannerDismissed => _bannerDismissed;

ConnectivityBloc(this._connectivity) : super(ConnectivityInitial()) {
_checkInitialConnectivity();

_connectivitySubscription =
_connectivity.onConnectivityChanged.listen((result) {
debugPrint('Connectivity changed: $result');
add(ConnectivityChanged(result != ConnectivityResult.none));
});

on<ConnectivityChanged>((event, emit) {
if (event.isConnected) {
emit(ConnectivityOnline());
} else {
emit(ConnectivityOffline());
}
});
}

Future<bool> _hasInternetConnection() async {
try {
final result = await InternetAddress.lookup('google.com');
return result.isNotEmpty && result[0].rawAddress.isNotEmpty;
} catch (e) {
return false;
}
}

Future<void> _checkInitialConnectivity() async {
try {
var connectivityResult = await _connectivity.checkConnectivity();
debugPrint('Initial connectivity check result: $connectivityResult');

bool isConnected = connectivityResult != ConnectivityResult.none &&
await _hasInternetConnection();
add(ConnectivityChanged(isConnected));
} catch (e) {
add(ConnectivityChanged(false));
}
}

@override
Future<void> close() {
_connectivitySubscription?.cancel();
return super.close();
}

@override
void onEvent(ConnectivityEvent event) {
if (event is ConnectivityBannerDismissed) {
_bannerDismissed = true;
}
super.onEvent(event);
}

Stream<ConnectivityState> mapEventToState(ConnectivityEvent event) async* {
if (event is ConnectivityChanged) {
yield event.isConnected ? ConnectivityOnline() : ConnectivityOffline();
} else if (event is ConnectivityBannerDismissed) {
_bannerDismissed = true;
yield state;
}
}
Comment on lines +71 to +78
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Avoid mixing on<Event> and mapEventToState in the same Bloc

Using both on<Event> handlers and overriding mapEventToState can lead to unexpected behavior. It's recommended to use one pattern consistently. Since you're already using on<Event> handlers, you can remove the mapEventToState method.

Apply this diff to remove the mapEventToState method:

   ...
-  Stream<ConnectivityState> mapEventToState(ConnectivityEvent event) async* {
-    if (event is ConnectivityChanged) {
-      yield event.isConnected ? ConnectivityOnline() : ConnectivityOffline();
-    } else if (event is ConnectivityBannerDismissed) {
-      _bannerDismissed = true;
-      yield state;
-    }
-  }

Committable suggestion skipped: line range outside the PR's diff.

}
19 changes: 19 additions & 0 deletions mobile-v3/lib/src/app/shared/bloc/connectivity_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
part of 'connectivity_bloc.dart';

abstract class ConnectivityEvent extends Equatable {
const ConnectivityEvent();

@override
List<Object?> get props => [];
}

class ConnectivityChanged extends ConnectivityEvent {
final bool isConnected;

const ConnectivityChanged(this.isConnected);

@override
List<Object?> get props => [isConnected];
}

class ConnectivityBannerDismissed extends ConnectivityEvent {}
18 changes: 18 additions & 0 deletions mobile-v3/lib/src/app/shared/bloc/connectivity_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
part of 'connectivity_bloc.dart';

abstract class ConnectivityState extends Equatable {
const ConnectivityState();

@override
List<Object?> get props => [];
}

class ConnectivityInitial extends ConnectivityState {}

class ConnectivityOnline extends ConnectivityState {}

class ConnectivityOffline extends ConnectivityState {
final bool isDismissed;

ConnectivityOffline([this.isDismissed = false]);
}
61 changes: 40 additions & 21 deletions mobile-v3/lib/src/app/shared/pages/no_internet_banner.dart
Original file line number Diff line number Diff line change
@@ -1,32 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:airqo/src/app/shared/bloc/connectivity_bloc.dart';

class NoInternetBanner extends StatelessWidget {
final VoidCallback onClose;
final VoidCallback? onClose;

const NoInternetBanner({Key? key, required this.onClose}) : super(key: key);
const NoInternetBanner({Key? key, this.onClose}) : super(key: key);

@override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Internet Connection Lost',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16.0,
return BlocBuilder<ConnectivityBloc, ConnectivityState>(
builder: (context, state) {
if (state is! ConnectivityOffline ||
(state).isDismissed) {
return SizedBox.shrink();
}

return Container(
color: Colors.red,
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: SafeArea(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Internet Connection Lost',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
overflow: TextOverflow.ellipsis,
),
),
IconButton(
icon: Icon(Icons.close, color: Colors.white),
onPressed: () {
context.read<ConnectivityBloc>().add(ConnectivityBannerDismissed());
onClose?.call();
},
),
Comment on lines +37 to +43
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Banner dismiss state does not persist across app restarts

The _bannerDismissed is initialized as a simple boolean variable without any persistence mechanism. It's set to false when the bloc is created and only stored in memory. When the app restarts, this state will be reset to its default value.

  • mobile-v3/lib/src/app/shared/bloc/connectivity_bloc.dart: The _bannerDismissed field is initialized to false and lacks any persistence implementation.
  • No SharedPreferences or other storage mechanisms are used to save this state.
🔗 Analysis chain

Verify dismiss behavior persistence.

The banner dismissal triggers both local and bloc state updates. We should verify if this state persists across app restarts.


The previous pattern didn't yield results, so I'm broadening the search to understand the connectivity-related state management and persistence mechanisms. This will help us verify if the dismiss state is actually persisted.


I noticed a _bannerDismissed variable being set in the previous output. Let's examine how this variable is initialized and managed to determine if the state persists across app restarts.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for persistence-related code in the ConnectivityBloc
ast-grep --pattern 'class ConnectivityBloc extends Bloc<ConnectivityEvent, ConnectivityState> {
  $$$
  isDismissed
  $$$
}'

Length of output: 126


Script:

#!/bin/bash
# Let's search for the ConnectivityBloc implementation and state handling
rg -l "ConnectivityBloc" --type dart

# Then look for persistence or storage related code in connectivity
rg "ConnectivityBannerDismissed|isDismissed" --type dart -C 3

# Search for any shared preferences or storage related to connectivity
rg "SharedPreferences.*connect" --type dart -C 2

Length of output: 4254


Script:

#!/bin/bash
# Let's check the ConnectivityBloc class implementation for any persistence variables
rg "_bannerDismissed" --type dart -C 5

# Also check if there's any initialization code that might restore state
cat mobile-v3/lib/src/app/shared/bloc/connectivity_bloc.dart

Length of output: 5104

],
),
),
IconButton(
icon: Icon(Icons.close, color: Colors.white),
onPressed: onClose,
),
],
),
);
},
);
}
}
}
32 changes: 32 additions & 0 deletions mobile-v3/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
connectivity_plus:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "876849631b0c7dc20f8b471a2a03142841b482438e3b707955464f5ffca3e4c3"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
connectivity_plus_platform_interface:
dependency: transitive
description:
name: connectivity_plus_platform_interface
sha256: "42657c1715d48b167930d5f34d00222ac100475f73d10162ddf43e714932f204"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
convert:
dependency: transitive
description:
Expand Down Expand Up @@ -198,6 +214,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.7"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
equatable:
dependency: "direct main"
description:
Expand Down Expand Up @@ -581,6 +605,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.0"
nm:
dependency: transitive
description:
name: nm
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
package_config:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions mobile-v3/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ dependencies:
google_maps_flutter: ^2.7.1
flutter_card_swiper: ^7.0.1
logger: ^2.5.0
connectivity_plus: ^6.1.0

dev_dependencies:
flutter_test:
Expand Down
Loading