Flutter/Dart - How to update Image after Image Picker on other Screens - flutter

I'm using Flutter's Image Picker plugin in order to allow the user to change their avatar. When they go to their account page they see their regular avatar photo with a camera icon on top. Clicking the camera icon will allow them to either take a photo from their camera or choose a new avatar from their gallery. After choosing the new one, the avatar photo automatically updates. However, when navigating away from their account page, the old avatar is visible throughout the rest of the app. I'm using Provider with a Change Notifier and Consumers for Avatars everywhere else. The problem though is that I can only access the Provider within a build so I don't know where I can call the Provider in my code. Add to this the fact that the Avatar I'm using all around the app comes from an internet url. After choosing with Image Picker, the new avatar photo gets uploaded to a server. The name of the new photo replaces the name of the old photo. Hence my app doesn't even know anything changed. Even reloading the pages doesn't work. However if I hot restart my app, the new avatar photo appears. Any ideas what I can do?
Here's the Image Picker code;
class Picker extends StatefulWidget {
Picker({Key key, this.title}) : super(key: key);
final String title;
#override
_PickerState createState() => _PickerState();
}
class _PickerState extends State<Picker>
with TickerProviderStateMixin,ImagePickerListener{
File _image;
AnimationController _controller;
ImagePickerHandler imagePicker;
#override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
imagePicker= ImagePickerHandler(this,_controller);
imagePicker.init();
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
var socialProvider = Provider.of<SocialProvider>(context);
return Container(
child: GestureDetector(
onTap: () => imagePicker.showDialog(context),
child: Center(
child: Stack(
children: <Widget>[
Center(
child: _image == null?
Consumer<SocialProvider>(
builder: (context, socialProvider, child) {
return
Image.network(socialProvider.currentavatar,
width: 200,
height: 200,
);
}) :
Container(
height: 200.0,
width: 200.0,
decoration: BoxDecoration(
color: Colors.grey,
image: DecorationImage(
image: FileImage(_image),
fit: BoxFit.cover,
),
),
),
),
Center(
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Container(
color: Colors.black26,
child: Icon(Icons.camera_alt,
color: Colors.white,
size: 40,
),
),
),
),
,
)
),
),
);
}
#override
userImage(File _image) async{
setState(() {
this._image = _image;
});
}
}
Currently the Consumers are correctly updating the avatars throughout the app whenever a user obtains a new avatar through logging in via social media. The new avatar is uploaded to the server and the ChangeNotifier is informed. The code for the Provider here is ;
Future<void> postSocialData(String avatar) async {
final url = "http://example.com/example.php&currentavatar=" + $avatar;
final response = await http.get(url);
if (response.statusCode == 200) {
currentavatar = "http://example.com/user.jpg";
var box = await Hive.openBox('currentuser');
box.put('currentavatar', "http://example.com/user.jpg",);
notifyListeners();
}
}
So I tried putting this into my Provider and calling it from an onTap function in the Image Picker build. Here's the onTap function;
GestureDetector(
onTap: () async {
String avatar = await _listener.openGallery(socialProvider.currentuserid);
String updatedavatar = "http://example.com/" + avatar;
socialProvider.updateAvatar(updatedavatar);
},
child: roundedButton(
"Gallery",
EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0),
const Color(0xFF167F67),
const Color(0xFFFFFFFF)),
),
And here's the Provider it calls;
Future<void> updateAvatar(String avatar) async {
var box = await Hive.openBox('currentuser');
box.put('currentavatar', avatar);
currentavatar = avatar;
notifyListeners();
}
But that didn't update the consumers with the new avatar. I guess because the external url for the avatar hasn't changed as the photo has simply been replaced and keeps the same name.

Using Hive's listener was a good idea. But it didn't help because it turns out the uploaded image - having the same URL as the replaced image - isn't being refreshed on the server-side. So the caching must be sorted out on the web server.

Related

I have problem with using condition to display an image

I am using image picker to get image from user then displayed in "CreateCard" UI.
The issue occur when i try using condition in my UI file, i need the condition so i can check if the file image is null before i can display it.
I am working with flutter GetX..
"CreateCard "UI Code:
GetBuilder<CreateCardContollerImp>(builder: (controller) =>UploadImage(
ontap: () {
showModalBottomSheet(
context: context,
builder: (context) {
return CreateCardBottomSheet(
uploadImageGallery: () {
controller.uploadImageGallery();
});
});
},
image: controller.image == null // Error occur here !
? AssetImage(AppImageAsset.profileimage)
: FileImage(controller.image),
),),
"UploadImage" Deifiniton:
class UploadImage extends StatelessWidget {
final void Function() ontap;
final ImageProvider<Object> image;
const UploadImage({super.key, required this.ontap, required this.image});
#override
Widget build(BuildContext context) {
return InkWell(
onTap: ontap,
child: Stack(children: [
Container(
width: 170,
height: 170,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
offset: Offset(0, 0),
color: AppColor.primaryColor,
blurRadius: 0,
),
],
borderRadius: BorderRadius.circular(100),
),
padding: const EdgeInsets.symmetric(vertical: 5),
child: CircleAvatar(
backgroundImage: image,
radius: MediaQuery.of(context).size.width - 310,
),
),
]),
);
}
}
"CreateCard" Controller:
class CreateCardContollerImp extends CreateCardContoller {
GlobalKey<FormState> formstate = GlobalKey<FormState>();
final imagepicker = ImagePicker();
late String imagePath;
late File image;
#override
uploadImageGallery() async {
final pickedimage = await imagepicker.getImage(source: ImageSource.gallery);
if (pickedimage != null) {
image = File(pickedimage.path);
imagePath = pickedimage.path;
update();
} else {
printError(info: "No image selected");
}
}
I was expecting this method will work fine.
you're logic looks fine, try casting the as ImageProvider:
controller.image == null
? AssetImage(AppImageAsset.profileimage) as ImageProvider
: FileImage(controller.image) as ImageProvider,
Another alternative is to try just using ImageProvider directly without unnecessary casting.
controller.image == null
? AssetImage(AppImageAsset.profileimage)
: NetworkImage(controller.image),
TheAssetImage is used in getting the avatar profile image from the app's asset.
The NetworkImage is used in getting the avatar profile image from the online DB or API.

How can I build a widget conditionally, based on various different futures that could be called from a few buttons?

I have created a reusable CircleAvatar widget which builds itself based on it's parameters. The parameters are using .watch() from riverpod & so update automatically. This translates to, in rough pseudo code:
No data (first/last name null && null avatar photo).....=> blank user icon avatar
Avatar image null && first/last name not null...........=> users initials in circle avatar
Avatar image not null...................................=> image avatar
Now I have three buttons, each button changes state which changes the parameters for the avatar as above. The buttons include futures like so:
Take picture.....=> image
Get photo file...=> image
Use initials.....=> image null (returns text initial avatar)
Problem
I don't have a clue how to architect this into future builder. The "takePicture" & "getPhotoFile" methods need a future builder minimally since they take some time. I can't use FutureBuilder(future: [futureOne, futureTwo, futureThree])) because not all futures should be called at once...
To be clear, my code is working right now. But crashes on strange situation going back and forth from initial avatar to image avatar. And loading image takes some time.
How can something like this be designed into a future builder or similar concept?
Reactive avatar widget:
class ActiveAvatarCircle extends StatefulWidget {
const ActiveAvatarCircle({
Key? key,
required double? radius,
required String? initials,
required ImageProvider<Object>? avatar,
}) : _radius = radius,
_initials = initials,
_avatar = avatar,
super(key: key);
final double? _radius;
final String? _initials;
final ImageProvider<Object>? _avatar;
#override
State<ActiveAvatarCircle> createState() => _ActiveAvatarCircleState();
}
class _ActiveAvatarCircleState extends State<ActiveAvatarCircle> {
#override
Widget build(BuildContext context) {
// Default blank circle avatar - awaiting users initials
if (widget._initials == null && widget._avatar == null) {
return CircleAvatar(
backgroundColor: ThemeEndpoints.avatarBackgroundColor(),
foregroundColor: ThemeEndpoints.avatarForegroundColor(),
radius: widget._radius ?? 80,
child: ThemeEndpoints.avatarDefaultIcon(),
);
}
// Initialed circle avatar awaiting avatar choice/no choice
if (widget._initials != null && widget._avatar == null) {
return CircleAvatar(
radius: widget._radius ?? 80,
backgroundColor: ThemeEndpoints.avatarBackgroundColor(),
foregroundColor: ThemeEndpoints.avatarForegroundColor(),
child: Text(
widget._initials!,
style: ThemeEndpoints.avatarTextTheme(),
),
);
}
// Avatar selected and overriding both default & initials avatar
return CircleAvatar(
radius: widget._radius ?? 80,
foregroundImage: widget._avatar,
);
}
}
Avatar section in my form code:
// Avatar section
Widget _avatarSection(
SignUpState signUpState,
BuildContext context,
String? firstName,
String? lastName,
ImageProvider<Object>? avatar,
) {
return SizedBox(
height: 150,
child: SizedBox(
width: 90.w,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ActiveAvatarCircle(
radius: null,
initials: (firstName != null && lastName != null)
? '${firstName[0]}${lastName[0]}'
: null,
avatar: avatar,
),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CustomButton(
buttonText: TextContent.of(context).authFormAvatarChoosePhotoText,
onPressed: () => _onGetImageFromFilesPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
CustomButton(
buttonText: TextContent.of(context).authFormAvatarTakePhotoText,
onPressed: () => _onGetImageFromCameraPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
CustomButton(
buttonText: TextContent.of(context).authFormAvatarInitialledText,
onPressed: () => _onUseInitialledAvatarPressed(signUpState),
width: SizeConfig.screenWidth! * 0.40,
),
],
),
],
),
),
);
}
// On get image from files pressed
Future<void> _onGetImageFromFilesPressed(SignUpState signUpState) async {
XFile? pickedFile = await ImagePicker().pickImage(
source: ImageSource.gallery,
maxWidth: 288,
maxHeight: 288,
imageQuality: 100,
);
if (pickedFile != null) {
final Uint8List bytes = await pickedFile.readAsBytes();
final MemoryImage image = MemoryImage(bytes);
signUpState.setAvatarStorageFormat(base64Encode(bytes));
signUpState.setAvatar(image);
pickedFile = null;
}
}
// On get get avatar from camera pressed
Future<void> _onGetImageFromCameraPressed(SignUpState signUpState) async {
XFile? cameraImage = await ImagePicker().pickImage(
source: ImageSource.camera,
maxWidth: 288,
maxHeight: 288,
imageQuality: 100,
);
if (cameraImage != null) {
final Uint8List bytes = await cameraImage.readAsBytes();
final MemoryImage image = MemoryImage(bytes);
signUpState.setAvatar(image);
signUpState.setAvatarStorageFormat(base64Encode(bytes));
}
}
// On use initialled avatar pressed
Future<void> _onUseInitialledAvatarPressed(SignUpState signUpState) async {
signUpState.setAvatar(null);
}

Flutter Google Map Markers are shown only after hot reload - using with cubit and custom marker

I'm struggling second day on this issue.
I use flutter google map to show about hundred custom markers with network image icons, that can be svg or png (using MarkerGenerator).
After opening the map page, MapCubit start to load items from API. In build i have BlocConsumer, where is listener, that build markers when loaded in that cubit and builder that build GoogleMap.
Problem is, that on first opening of page there are no images in markers, only white circle. When I tried to set one image url to all markers, it was loaded properly. Then, when i go on previous page or hot reload (not always), icons are there. On same page i have legend, that draw images from same urls, where images are set properly in most times. Sometimes it is need to go back and forward more times.
I can load icons after click on item in filter, that calls MapCubit, too.
I dont know, if it means something, but next problem, what i have is, that on release and appbundle build, no map is shown, only grey screen, buttons on side and google logo on bottom left.
I tried many tips on internet, but nothing helped.
Preview of first opening of MapPage
Preview of filter at first opening (has all icons)
Preview of second opening of MapPage
Preview of third opening of MapPage (has all icons)
MapPage (MarkerGenerator is in listener and initState becouse of two different uses that needs it)
class _MapAreaState extends State<MapArea> {
MapCubit _mapCubit;
Set<Marker> markers = {};
List<CustomMarker> markerWidgets = [];
bool markersLoaded = false;
#override
void initState() {
_mapCubit = BlocProvider.of<MapCubit>(context);
markers = {};
MarkerGenerator(
_mapCubit.state.items.map((e) => CustomMarker(type: e.type)).toList(),
(bitmaps) {
setState(() {
bitmaps.asMap().forEach((mid, bmp) {
IMapItem item = _mapCubit.state.items[mid];
markers.add(Marker(
markerId: MarkerId(item.title),
position: item.latLng,
icon: BitmapDescriptor.fromBytes(bmp),
// markerId: MarkerId(item.title),
// position: item.latLng,
onTap: () async {
await _mapCubit.showDetail(item);
}));
});
});
}).generate(context);
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
color: tercialBackgroundColor,
child: BlocConsumer<MapCubit, MapState>(
bloc: _mapCubit,
listener: (context, state) {
if (state.changedItems && state.items.isNotEmpty) {
markerWidgets = _mapCubit.state.items
.map((e) => CustomMarker(type: e.type))
.toList();
markers = {};
MarkerGenerator(markerWidgets, (bitmaps) {
setState(() {
bitmaps.asMap().forEach((mid, bmp) {
log(bmp.toString());
IMapItem item = _mapCubit.state.items[mid];
markers.add(Marker(
markerId: MarkerId(item.title),
position: item.latLng,
icon: BitmapDescriptor.fromBytes(bmp),
// markerId: MarkerId(item.title),
// position: item.latLng,
onTap: () async {
await _mapCubit.showDetail(item);
}));
});
});
}).generate(context);
}
},
builder: (context, state) {
return Stack(
children: [
GoogleMap(
zoomControlsEnabled: false,
compassEnabled: false,
markers: markers,
// markers: Set<Marker>.of(state.markers),
initialCameraPosition: CameraPosition(
target: state.items.length == 1
? state.items[0].latLng
: LatLng(49.07389317899512, 19.30980263713778),
zoom: 8.5,
),
minMaxZoomPreference: MinMaxZoomPreference(8, 22),
cameraTargetBounds: CameraTargetBounds(LatLngBounds(
northeast: LatLng(50.16477808289659, 20.56397637952818),
southwest: LatLng(48.75267922516721, 18.76330228064009),
)),
onMapCreated: (GoogleMapController controller) {
if (!_mapCubit.controller.isCompleted) {
rootBundle
.loadString('assets/googleMapsStyle.json')
.then((string) async {
controller.setMapStyle(string);
});
_mapCubit.controller.complete(controller);
log(_mapCubit.controller.toString());
log(controller.toString());
setState(() {
});
}
},
),
// if(state.items.isEmpty)
// FullScreenLoadingSpinner()
],
);
},
),
);
}
}
CustomMarker class
class CustomMarker extends StatelessWidget {
final ItemType type;
const CustomMarker({Key key, this.type}) : super(key: key);
#override
Widget build(BuildContext context) {
// precachePicture(
// svgPicture.pictureProvider,
// Get.context!,
// );
// if(type.icon is /-
return Stack(
clipBehavior: Clip.none,
children: [
Icon(
Icons.add_location,
color: type.color,
size: 56,
),
Positioned(
left: 16,
top: 10,
child: Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: primaryBackgroundColor,
borderRadius: BorderRadius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Center(child: type.icon),
),
),
),
],
);
}
}
Icon setting in ItemType factory, that is used in CustomMarker
icon: map['icon'] != null
? (map['icon'] is Image
? map['icon']
: (map['icon'].substring(map['icon'].length - 4) == '.svg'
? WebsafeSvg.network(
map['icon'],
width: 18,
height: 18,
color: Colors.black,
placeholderBuilder: (BuildContext context) => Container(
padding: const EdgeInsets.all(30.0),
child: const CircularProgressIndicator()),
)
: Image.network(map['icon'])))
Lately somewhen this exception is in console
======== Exception caught by image resource service =====================
The following HttpException was thrown resolving an image codec:
, uri = https://www.xxx.sk/images/svgs/culture.png
When the exception was thrown, this was the stack:
Image provider: NetworkImage("https://www.xxx.sk/images/svgs/culture.png", scale: 1.0)
Image key: NetworkImage("https://www.xxx.sk/images/svgs/culture.png", scale: 1.0)
I dont know, what all to send, so far at least this. Thanks.

LateInitializationError: Field 'displayName' has not been initialized in Flutter's Shared Prefrence?

I am creating an Flutter application in which i need to store the user information and for some extent i am able to achieve it with Shared Prefrence.
i am storing the data at login screen as follows:
var displayName=jsondata["username"];
SharedPreferences prefs=await SharedPreferences.getInstance();
prefs.setString('displayName', displayName);
and on my drawerScreen i am getting the same data as follows:
late String displayName;
initState() {
getData();
super.initState();
}
getData() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
displayName=prefs.getString('displayName')!;
}
but whenever i click on drawer button there is an error as shown:
LateInitializationError: Field 'displayName' has not been initialized.
but when i hot reload my app while drawer is opened there is no error when i go to other screen and get back to drawer the LateInitialization errro arises,
i dont know what causing this error
and this is the other screen where i am shifting from drawer:
late String displayName;
getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
displayName=prefs.getString('displayName')!;
print(displayName);
}
initState() {
getData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Theme.of(context).backgroundColor,
key: _scaffoldKey,
drawer: SizedBox(
width: MediaQuery.of(context).size.width * 0.75 < 400 ?
MediaQuery.of(context).size.width * 0.75 : 350,
child: Drawer(
child: AppDrawer(
selectItemName: 'Home',
),
),
),
appBar: AppBar(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
automaticallyImplyLeading: false,
title: Row(
children: <Widget>[
SizedBox(
height: AppBar().preferredSize.height,
width: AppBar().preferredSize.height + 40,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
alignment: Alignment.centerLeft,
child: GestureDetector(
onTap: () {
_scaffoldKey.currentState!.openDrawer();
},
child: Icon(
Icons.dehaze,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),
),
),
],
),
),
can someone suggest me a better way of doing it . Thanks in advance <3
Your problem is that initState is not async (and it can't be async), so your getData will likely (or surely) be completed later than the build method is called. Since you set displayName in getData, and you marked it as late, you will get this error. That's why it is working after hot reload, because at that time displayName is already initalized.
One possible solution is to call SharedPreferences.getInstance() (this is why your getData needs to be async) somewhere earlier in your application, for example in main() async function, store the result of getInstance using a ChangeNotifierProvider (or any other way that allows you to use this value in this widget), and use its value in getData. This way getData does not need to be async, and displayName will be initalized before build method is called. This can be useful if you use SharedPreferences in more than one place, because you don't have to await the result of getInstance each time.
Another approach is not declaring displayName as late, but allow it to be null, and then in the build method you can use a FutureBuilder which completes upon getData is executed, and the building of Scaffold will only be started after this future completes. In this case you can display a progress indicator while the future is not completed.
I forgot to to add setState();
late String displayName;
initState() {
getData();
super.initState();
}
Future getData() async{
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
displayName=prefs.getString('displayName')!;
});
}

TRYING TO PLACE CALL DIRECTLY FROM FLUTTER: MissingPluginException(No implementation found for method callNumber

I want my app to place an automated call to a specific number when a certain condition arises. The two popular plugins are flutter_phone_direct_caller and url_launcher. Url launcher's problem is that the method will push the number to the dialer of your phone but wont start the call but flutter_phone_direct_caller claims it will initiate. This is the example in their documentation.
import 'package:flutter/material.dart';
import 'package:flutter_phone_direct_caller/flutter_phone_direct_caller.dart';
void main() {
runApp(Scaffold(
body: Center(
child: RaisedButton(
onPressed: _callNumber,
child: Text('Call Number'),
),
),
));
}
_callNumber() async{
const number = '08592119XXXX'; //set the number here
bool res = await FlutterPhoneDirectCaller.callNumber(number);
}
this is the code for my page..when the button is pressed, call should initiate but for me, it returns an error.(my phone no. is XXXd out ,when i ran it i put in my actual no).
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:vitality/components/bottomAppBar.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:vitality/components/biom.dart';
import 'package:flutter_phone_direct_caller/flutter_phone_direct_caller.dart';
class HomeScreen extends StatefulWidget {
static const String id = 'home_screen';
final String docid;
final bool isCaretaker;
HomeScreen({#required this.docid, #required this.isCaretaker});
#override
_HomeScreenState createState() => _HomeScreenState();
}
_callNumber() async {
const number = '86065XXXXX'; //set the number here
bool res = await FlutterPhoneDirectCaller.callNumber(number);
}
class _HomeScreenState extends State<HomeScreen> {
final auth = FirebaseAuth.instance;
var pulse;
var temp;
#override
Widget build(BuildContext context) {
print('got here');
print(auth.currentUser.uid);
String id = ModalRoute.of(context).settings.arguments;
CollectionReference main = FirebaseFirestore.instance.collection('maindb');
main.doc(id).get().then((DocumentSnapshot documentSnapshot) {
if (documentSnapshot.exists) {
print('Document exists on the database');
pulse = documentSnapshot['pulse'];
temp = documentSnapshot['temperature'];
}
});
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Color(0xFF602247),
toolbarHeight: 50.0,
centerTitle: true,
title: Text(
'HEALTH TRACKER',
style: Theme.of(context).textTheme.headline4,
)),
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image:
NetworkImage('https://cdn.wallpapersafari.com/12/24/GiZRfh.jpg'),
fit: BoxFit.cover,
colorFilter: new ColorFilter.mode(
Colors.black.withOpacity(.7), BlendMode.dstATop),
)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Text(widget.docid),
Text({widget.isCaretaker}.toString()),
biom(which: 'pulse', image: 'pulse', docid: widget.docid),
RoundBorderText(text: 'PULSE'),
biom(which: 'temperature', image: 'temper', docid: widget.docid),
RoundBorderText(text: 'TEMPERATURE'),
SizedBox(height: 30.0),
FlatButton(
child: Text('test call'),
onPressed: () async {
FlutterPhoneDirectCaller.callNumber('5');
})
]),
),
bottomNavigationBar: bottomAppBar(id: widget.docid),
);
}
}
class RoundBorderText extends StatelessWidget {
final String text;
RoundBorderText({this.text});
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(
left: 40.0, right: 40.0, top: 8.0, bottom: 8.0),
decoration: BoxDecoration(
// border: Border.all(
// color: Colors.black,
// width: 1.0,
// ),
borderRadius: BorderRadius.all(Radius.circular(20))),
child: Text(text, style: Theme.of(context).textTheme.headline1));
}
}
E/flutter (23210): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: MissingPluginException(No implementation found for method callNumber on channel flutter_phone_direct_caller)
This plugin has 94% popularity so it works for most people. Does anyone know what the issue is?
The way flutter integrates with native functionality is that is creates what are called MethodChannels using which they can call functions that are registered inside native java code from dart.
So one reason this error might be coming is that your flutter code is not able to communicate with the native java code, means it is not finding any channel or it is not finding the method registered by the package through a channel there.
I suspect this could be a build issue.
Steps
Uninstall the app from your device.
Rebuild app again.
This should solve the issue.