Series 2 of 2 — Part 3 of 4
Building DevFeed — Part 3: In-app WebView, Sharing Actions, and the INTERNET Permission
Series: Building DevFeed — A Proper Flutter MVVM App from Day One
With the feed working, the obvious next feature was letting users read articles. An in-app WebView gives a better experience than the system browser — you can add a progress indicator, handle navigation events, and keep users inside the app.
This post covers the WebView implementation, sharing actions, and a debugging lesson about Android permissions that cost me more time than it should have.
The WebView screen
I used the webview_flutter package. I made ArticleWebViewScreen a ConsumerStatefulWidget rather than ConsumerWidget because the WebViewController needs to be initialised in initState and stored as a widget field. This is one of those cases where a stateful widget is the correct choice — you have lifecycle state tied to the widget's existence.
class ArticleWebViewScreen extends ConsumerStatefulWidget {
final String url;
final String title;
const ArticleWebViewScreen({
super.key,
required this.url,
required this.title,
});
@override
ConsumerState<ArticleWebViewScreen> createState() => _ArticleWebViewScreenState();
}
Refactoring state management for the WebView
My initial implementation stored the page loading state in the widget's own state using setState. This worked but felt inconsistent with the rest of the app. I refactored to move the loading state into a Riverpod StateProvider, making it reactive and consistent with how the rest of the app handled state.
Sharing actions
I added two sharing actions to the app bar: share the article URL using share_plus, and copy to clipboard using Flutter's Clipboard.setData. Small feature, but a news reader without sharing feels incomplete.
IconButton(
icon: const Icon(Icons.share),
onPressed: () => Share.share('${widget.title} ${widget.url}'),
),
The INTERNET permission — a debugging story
I ran the app on Android, tapped an article, and the WebView showed a blank screen. No error, no crash. I checked the WebViewController error callback — nothing. I checked the URL, the network, added logging to the navigation delegate. The page load was starting and immediately failing.
Eventually I checked AndroidManifest.xml and found the problem: there was no INTERNET permission.
<uses-permission android:name="android.permission.INTERNET"/>
One line. The WebView was silently failing to load URLs because the app had no permission to access the internet on Android.
This is easy to miss because packages like Dio and http add this permission automatically via manifest merging. webview_flutter does not — you have to add it manually.
The lesson
When something fails silently on Android, check the manifest before spending an hour on code-level debugging. INTERNET, CAMERA, READ_EXTERNAL_STORAGE — these can all cause silent failures that look like bugs in your code when they are actually missing permissions.
What I learned
-
Stateful vs Riverpod: Use
ConsumerStatefulWidgetwhen you have widget lifecycle state and Riverpod state in the same screen. -
Manifest gotchas:
webview_flutterrequires theINTERNETpermission to be added manually toAndroidManifest.xml. - Silent networking failures: Check the Android manifest early when networking behaves unexpectedly.
Next: Part 4 — Shipping v1.0.1 with GitHub Actions and a Changelog
Top comments (0)