Flutter Test Mock GraphQL Mutation result - flutter

I'm trying to create widget tests for a flutter application using GraphQL.
What I want to do is to test the behaviour of the app which depends on the result of a GraphQL Mutation on a user action.
This is a very simple example of the app:
class FirstScreen extends StatelessWidget {
#override
Widget return Container(
child: Mutation(
options: myMutationOptions,
onCompleted: (dynamic result) {
final bool myBool = result['bool'] as bool;
if (myBool) {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SecondScreen()));
} else {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => ThirdScreen()));
}
},
builder: (RunMutation runMutation, QueryResult queryResult) {
return FlatButton(
child: Text('Button'),
onPressed: () async {
await runMutation(myParameters).networkResult;
},
);
},
),
);
}
What I would like to do is to mock the result of the mutation so in my widget tests, I can test that the button redirects to the SecondScreen or ThirdScreen depending of the result myBool.
How can I do that ?

I finally managed to successfully mock a GraphQL Mutation. Here is how I did it, it is inspired from #Gpack's comment but I had to add some modifications and details to it.
To make it easy to use I created a wrapper widget GraphQLMutationMocker :
class MockClient extends Mock implements Client {
MockClient({
this.mockedResult,
this.mockedStatus = 200,
});
final Map<String, dynamic> mockedResult;
final int mockedStatus;
#override
Future<StreamedResponse> send(BaseRequest request) {
return Future<StreamedResponse>.value(
StreamedResponse(
Stream.value(utf8.encode(jsonEncode(mockedResult))),
mockedStatus,
),
);
}
}
class GraphQLMutationMocker extends StatelessWidget {
const GraphQLMutationMocker({
#required this.child,
this.mockedResult = const {},
this.mockedStatus = 200,
this.url = 'http://url',
this.storagePrefix = 'test',
});
final Widget child;
final Map<String, dynamic> mockedResult;
final int mockedStatus;
final String url;
final String storagePrefix;
#override
Widget build(BuildContext context) {
final mockClient = MockClient(
mockedResult: mockedResult,
mockedStatus: mockedStatus,
);
final httpLink = HttpLink(
uri: url,
httpClient: mockClient,
);
final graphQLClient = ValueNotifier(
GraphQLClient(
cache: InMemoryCache(storagePrefix: storagePrefix),
link: httpLink,
),
);
return GraphQLProvider(
client: graphQLClient,
child: child,
);
}
}
Then it was pretty easy to write the tests
group('Test mutation', () {
testWidgets('It should redirect to SecondScreen', (WidgetTester tester) async {
await tester.pumpWidget(GraphQLMutationMocker(
mockedResult: <String, dynamic>{
'data': {
'bool': true,
},
},
child: FirstScreen(),
));
// Click on button
await tester.tap(find.text('Button'));
await tester.pumpAndSettle();
// Check I'm on the right screen
expect(find.byType(SecondScreen), findsOneWidget);
expect(find.byType(ThirdScreen), findsNothing);
});
testWidgets('It should redirect to ThirdScreen', (WidgetTester tester) async {
await tester.pumpWidget(GraphQLMutationMocker(
mockedResult: <String, dynamic>{
'data': {
'bool': false,
},
},
child: FirstScreen(),
));
// Click on button
await tester.tap(find.text('Button'));
await tester.pumpAndSettle();
// Check I'm on the right screen
expect(find.byType(SecondScreen), findsNothing);
expect(find.byType(ThirdScreen), findsOneWidget);
});
})

Create a mock of http.Client like in the flutter docs
In your test, wrap your FirstScreen in a GraphqlProvider like so:
class MockHttpClient extends Mock implements Client {}
group('Test mutation', () {
MockHttpClient mockHttpClient;
HttpLink httpLink;
ValueNotifier<GraphQLClient> client;
setUp(() async {
mockHttpClient = MockHttpClient();
httpLink = HttpLink(
uri: 'https://unused/graphql',
httpClient: mockHttpClient,
);
client = ValueNotifier(
GraphQLClient(
cache: InMemoryCache(storagePrefix: 'test'),
link: httpLink,
),
);
});
testWidgets('redirects to SecondScreen', (WidgetTester tester) async {
when(client.send(captureAny)).thenAnswer(/* ... */);
await tester.pumpWidget(GraphQLProvider(
client: client,
child: FirstScreen(),
));
// Click on button
verify(mockHttpClient.send(any)).called(1);
// etc.
});
})

Related

Debugging authStateChanges() stream leading to deauthentication due to async call not finishing on time

The previous developer has implemented an authentication mechanism that's very hit or miss when it comes to successfully authenticating users. I've been trying to debug the issues, but this last one persists.
The logic for the authentication is the following:
Users opens the app and is navigated to the splash screen
The splash screen's cubit makes an async request to get an AuthModel that contains the Firebase user's UUID and if successful, emits an "already logged in" state
The splash screen then reacts to the state and navigates the user to the main screen
The main screen seems to listen for event emissions by the AppBloc & MainBloc in order to determine if the user can be shown the actual content.
The issue is that although steps (1), (2) & (3) seem to initially work, after running the app in debug mode a lot, the AppBloc's on<AppEventAuthModelChanged$>(_onUserChanged); runs after receiving a new AuthModel emission and inside the _onUserChanged function, we land on the return emit(const AppState.unauthenticated()) statement. After observing the debugger, this is caused because the _authRepository.token != null expression is false at that point (but becomes true later).
This token is retrieved via the await _connectToServer(firebaseUser: firebaseUser); call inside the Auth Repository Impl's_ensureUser() function that runs based on the authStateChanges() stream via the getter.
Now, I have observed that the _ensureUser() is run multiple times and always seems to emit a valid Firebase user. However, the call to the _connectToServer() seems to take too long to finish (in order to initialize the token field), and thus the AppBloc's logic in the _onUserChanged call fails. What would be a valid approach so that the call has finished in order for us to be sure that the token was either retrieved or that something actually went wrong?
Is the code unsalvageable?
Splash_component.dart for the splash_screen.dart:
class SplashComponent extends StatefulWidget {
const SplashComponent({Key? key}) : super(key: key);
#override
State<SplashComponent> createState() => _SplashComponentState();
}
class _SplashComponentState extends State<SplashComponent> {
SplashCubit get _cubit => context.read<SplashCubit>();
bool _isTextVisible = false;
#override
void initState() {
super.initState();
_cubit.init();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.red,
body: BlocConsumer<SplashCubit, SplashState>(
listener: (BuildContext context, SplashState state) {
state.maybeWhen(
newUser: () async {
setState(() => _isTextVisible = true);
//await Future<void>.delayed(const Duration(seconds: 1));
NavigatorUtils.replaceToAuthScreen(context);
},
alreadyLoggedIn: () async {
setState(() => _isTextVisible = true);
//await Future<void>.delayed(const Duration(seconds: 1));
NavigatorUtils.replaceToMainScreen(context);
},
orElse: () {},
);
},
builder: (BuildContext context, SplashState state) {
return state.maybeWhen(
orElse: () {
return Padding(
padding: const EdgeInsets.all(40),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset('assets/images/blob_logo_white_splash.png'),
const SizedBox(height: 24),
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: _isTextVisible ? 1.0 : 0.0,
child: Text(
'Carry your art with you',
style: montserratRegular22.copyWith(
color: AppColors.white),
),
),
],
),
);
},
);
},
),
);
}
}
Splash_cubit.dart:
class SplashCubit extends Cubit<SplashState> {
SplashCubit({required AuthRepository authRepository})
: _authRepository = authRepository,
super(const SplashState.loading());
final AuthRepository _authRepository;
Future<void> init() async {
try {
final AuthModel? authModel = await _authRepository.authModel.first;
if (authModel != null) {
return emit(const SplashState.alreadyLoggedIn());
}
return emit(const SplashState.newUser());
} catch (_) {
return emit(const SplashState.newUser());
}
}
}
Main_component.dart for the main_screen.dart:
const double _iconSize = 24;
class MainComponent extends StatelessWidget {
const MainComponent({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<AppBloc, AppState>(
listenWhen: (AppState previous, AppState current) {
return previous is AppStateAuthenticated$ &&
current is AppStateUnauthenticated$;
},
listener: (BuildContext context, AppState state) {
state.maybeWhen(
unauthenticated: () => NavigatorUtils.replaceToAuthScreen(context),
orElse: () {},
);
},
child: BlocBuilder<MainCubit, MainState>(
builder: (BuildContext context, MainState state) {
final MainCubit cubit = context.read<MainCubit>();
final AppState appState = context.watch<AppBloc>().state;
final TabType tab = state.tab;
return Scaffold(
body: appState.maybeWhen(
authenticated: (_) {
return SafeArea(
child: IndexedStack(
index: tab.index,
children: const <Widget>[
AlbumScreen(),
CameraScreen(),
ExploreScreen(),
],
),
);
},
unauthenticated: () {
NavigatorUtils.replaceToAuthScreen(context);
return const SizedBox.shrink();
},
orElse: () => const SizedBox.shrink(),
),
bottomNavigationBar: BottomNavigationBar(
onTap: (int index) => cubit.changeTab(TabType.values[index]),
currentIndex: cubit.state.tab.index,
elevation: 0,
selectedItemColor: AppColors.red,
selectedFontSize: 12,
items: <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Image.asset(TabType.album.asset,
color: AppColors.grey,
height: _iconSize,
width: _iconSize),
activeIcon: Image.asset(TabType.album.asset,
color: AppColors.red,
height: _iconSize,
width: _iconSize),
label: 'ALBUM',
),
BottomNavigationBarItem(
icon: Image.asset(
TabType.camera.asset,
color: AppColors.grey,
height: _iconSize,
width: _iconSize,
),
activeIcon: Image.asset(
TabType.camera.asset,
color: AppColors.red,
height: _iconSize,
width: _iconSize,
),
label: 'CAMERA',
),
BottomNavigationBarItem(
icon: Image.asset(
TabType.explore.asset,
color: AppColors.grey,
height: _iconSize,
width: _iconSize,
),
activeIcon: Image.asset(
TabType.explore.asset,
color: AppColors.red,
height: _iconSize,
width: _iconSize,
),
label: 'EXPLORE',
),
],
),
);
},
),
);
}
}
app_bloc.dart:
class AppBloc extends Bloc<AppEvent, AppState> {
AppBloc({
required AuthRepository authRepository,
required UserRepository userRepository,
required ArtworkRepository artworkRepository,
required ArtistRepository artistRepository,
required GalleryRepository galleryRepository,
required VenueRepository venueRepository,
}) : _authRepository = authRepository,
_userRepository = userRepository,
_artworkRepository = artworkRepository,
_artistRepository = artistRepository,
_galleryRepository = galleryRepository,
_venueRepository = venueRepository,
super(const AppState.initial()) {
on<AppEventAuthModelChanged$>(_onUserChanged);
on<AppEventLogout$>(_onLogout);
_authModelSubscription = _authRepository.authModel.listen(
(AuthModel? authModel) => add(AppEvent.authModelChanged(authModel)),
);
}
final AuthRepository _authRepository;
final UserRepository _userRepository;
final ArtworkRepository _artworkRepository;
final ArtistRepository _artistRepository;
final GalleryRepository _galleryRepository;
final VenueRepository _venueRepository;
late StreamSubscription<AuthModel?> _authModelSubscription;
Future<void> _onUserChanged(
AppEventAuthModelChanged$ event, Emitter<AppState> emit) async {
final AuthModel? authModel = event.authModel;
if (authModel != null &&
state is! AppStateUnauthenticated$ &&
_authRepository.token != null) {
final String token = _authRepository.token!;
_userRepository.token = token;
_artworkRepository.token = token;
_artistRepository.token = token;
_galleryRepository.token = token;
_venueRepository.token = token;
await _artworkRepository.getSavedArtworks();
final User user = await _userRepository.getUserDetails();
return emit(AppState.authenticated(user: user));
} else {
_authRepository.token = null;
_artworkRepository.token = null;
_artistRepository.token = null;
return emit(const AppState.unauthenticated());
}
}
Future<void> _onLogout(AppEventLogout$ event, Emitter<AppState> emit) async {
unawaited(_authRepository.logOut());
emit(const AppState.unauthenticated());
}
#override
Future<void> close() {
_authModelSubscription.cancel();
return super.close();
}
}
auth_repository_impl.dart:
class AuthRepositoryImpl implements AuthRepository {
AuthRepositoryImpl({required DioClient dioClient}) : _client = dioClient;
final DioClient _client;
final auth.FirebaseAuth _auth = auth.FirebaseAuth.instance;
#override
String? token;
bool isFetchingToken = false;
#override
Stream<AuthModel?> get authModel {
return MergeStream<auth.User?>(
<Stream<auth.User?>>[
_auth.authStateChanges(),
],
)
.startWith(_auth.currentUser) //
.switchMap<AuthModel?>(_ensureUser)
.share()
.distinct();
}
Stream<AuthModel?> _ensureUser(auth.User? firebaseUser) async* {
if (firebaseUser == null) {
yield* Stream<AuthModel?>.value(null);
return;
}
if (token == null && !isFetchingToken) {
await _connectToServer(firebaseUser: firebaseUser);
}
yield* Stream<AuthModel?>.value(AuthModel(uid: firebaseUser.uid));
}
Future<void> _connectToServer(
{auth.UserCredential? userCredential, auth.User? firebaseUser}) async {
try {
String? firebaseToken;
isFetchingToken = true;
if (userCredential != null) {
firebaseToken = await userCredential.user?.getIdToken();
} else {
firebaseToken = await firebaseUser?.getIdToken();
}
final String email =
userCredential?.user?.email ?? firebaseUser?.email ?? '';
final String baseUrl = getIt<AppRepository>().env == Env.staging
? stagingUrl
: productionUrl;
final ApiResponse response = await _client.httpCall(
baseUrl: baseUrl,
path: '/mobile/login',
httpMethod: HttpMethod.POST,
queryParameters: <String, dynamic>{
'email': email,
'token': firebaseToken,
},
);
final Map<String, dynamic> data = response.data;
token = data['message'];
isFetchingToken = false;
} on DioError catch (ex) {
if (ex.type == DioErrorType.connectTimeout) {
throw Exception('Connection Timeout Exception');
}
throw Exception(ex.message);
}
}
#override
Future<void> logOut() async => await _auth.signOut();
#override
Future<void> registerWithEmailAndPassword(
{required String email, required String password}) async {
final auth.UserCredential userCredential =
await _auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
await _connectToServer(userCredential: userCredential);
}
#override
Future<void> loginWithApple() async {
final String rawNonce = NonceGenerator.generateNonce();
final String nonce = NonceGenerator.sha256ofString(rawNonce);
final AuthorizationCredentialAppleID appleCredential =
await SignInWithApple.getAppleIDCredential(
scopes: <AppleIDAuthorizationScopes>[
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName
],
nonce: nonce,
);
final auth.OAuthCredential oauthCredential =
auth.OAuthProvider('apple.com').credential(
idToken: appleCredential.identityToken,
rawNonce: rawNonce,
);
final auth.UserCredential userCredential =
await auth.FirebaseAuth.instance.signInWithCredential(oauthCredential);
await _connectToServer(userCredential: userCredential);
}
#override
Future<void> loginWithEmailAndPassword(
{required String email, required String password}) async {
try {
final auth.UserCredential userCredential =
await _auth.signInWithEmailAndPassword(
email: email,
password: password,
);
await _connectToServer(userCredential: userCredential);
log('z1z:: Server token $token');
} catch (e) {
rethrow;
}
}
#override
Future<void> resetPassword({required String email}) async =>
await _auth.sendPasswordResetEmail(email: email);
}

Stripe Payment not going through with Flutter

I'm trying to test a payment gateway method with stripe and I have this error when i try to click the button which it said "[log] {success: false, error: o is not defined}", here is my code. I'm not sure what or how to solve this but I would really need some help on this one. Also I included with the screenshot of the problem
Main.dart
import 'dart:convert';
import 'dart:developer';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_stripe/flutter_stripe.dart';
import 'package:http/http.dart' as http;
Future <void> main() async{
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: FirebaseOptions(apiKey: "AIzaSyD7Uhhiv85NCcDjoTEQ-K7T5qU4bNXci8M",
appId: "XXX", messagingSenderId: "XXX",
projectId: "itt632-56973",
));
Stripe.publishableKey = "pk_test_51LHIjvJ5bt4B4CmJ3xTspeufDqv4VojZlmhuBaVvjhUoDbJaoKziiUkFZPOTl3a4CbVcfdmnJKosItZiiweRYfvF00aOI2xKIS";
Stripe.instance.applySettings();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Demo(),
);
}
}
// 'https://us-central1-itt632-56973.cloudfunctions.net/stripePaymentIntentRequest'),
class Demo extends StatelessWidget {
const Demo({Key? key}) : super(key: key);
Future<void> initPayment(
{required String email,
required double amount,
required BuildContext context}) async {
try {
// 1. Create a payment intent on the server
final response = await http.post(
Uri.parse(
'https://us-central1-itt632-56973.cloudfunctions.net/stripePaymentIntentRequest'),
body: {
'email': email,
'amount': amount.toString(),
});
final jsonResponse = jsonDecode(response.body);
log(jsonResponse.toString());
// 2. Initialize the payment sheet
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
paymentIntentClientSecret: jsonResponse['paymentIntent'],
merchantDisplayName: 'Grocery Flutter course',
customerId: jsonResponse['customer'],
customerEphemeralKeySecret: jsonResponse['ephemeralKey'],
testEnv: true,
merchantCountryCode: 'SG',
));
await Stripe.instance.presentPaymentSheet();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Payment is successful'),
),
);
} catch (errorr) {
if (errorr is StripeException) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('An error occured ${errorr.error.localizedMessage}'),
),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('An error occured $errorr'),
),
);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: const Text('Pay 50\$'),
onPressed: () async {
await initPayment(
amount: 50.0, context: context, email: 'JEFRILUQMAN#test.com');
},
)),
);
}
}
Here is the index.js function for the stripe
const functions = require("firebase-functions");
const stripe = require("stripe")("sk_test_51LHIjvJ5bt4B4CmJclvzNcSaSPICuutFIjmg3GZb1sv6fySDehLAhvHOW2hl1W7EWXYM5oqfHkHZyJDeB36rIJpf00ksfQJnWa");
exports.stripePaymentIntentRequest = functions.https.onRequest(async (req, res) => {
try {
let customerId;
//Gets the customer who's email id matches the one sent by the client
const customerList = await stripe.customers.list({
email: req.body.email,
limit: 1
});
//Checks the if the customer exists, if not creates a new customer
if (customerList.data.length !== 0) {
customerId = customerList.data[0].id;
}
else {
const customer = await stripe.customers.create({
email: req.body.email
});
customerId = customer.data.id;
}
//Creates a temporary secret key linked with the customer
const ephemeralKey = await stripe.ephemeralKeys.create(
{ customer: customerId },
{ apiVersion: '2020-08-27' }
);
//Creates a new payment intent with amount passed in from the client
const paymentIntent = await stripe.paymentIntents.create({
amount: parseInt(req.body.amount),
currency: 'usd',
customer: customerId,
})
res.status(200).send({
paymentIntent: paymentIntent.client_secret,
ephemeralKey: ephemeralKey.secret,
customer: customerId,
success: true,
})
} catch (error) {
res.status(404).send({ success: false, error: error.message })
}
});
Screenshot
Screenshot2

Widget test failing when passing already created bloc instance to provider

I'm passing the CartBloc instance to the BlocProvider. But the test is failing as the ListView widget is not found.
Below is the test code.
void main() {
late MockCartRepository cartRepository;
late MockSharedPreferenceService sharedPreferenceService;
late AuthBloc authBloc;
late MockUserRepository userRepository;
late CartData cartData;
late CartBloc cartBloc;
setUp(() {
cartData = CartData.fromJson(guestCartData);
cartRepository = MockCartRepository();
sharedPreferenceService = MockSharedPreferenceService();
userRepository = MockUserRepository();
authBloc = AuthBloc(
service: sharedPreferenceService,
userRepository: userRepository,
);
cartBloc = CartBloc(
cartRepository: cartRepository,
sharedPreferenceService: sharedPreferenceService,
authBloc: authBloc,
);
});
_pumpTestWidgetWithEvent(WidgetTester tester, CartEvent event) {
return MaterialApp(
home: BlocProvider(
create: (_) => cartBloc..add(event),
child: const CartView(),
),
);
}
group('Testing cart view', () {
testWidgets(
'Adding item to cart',
(WidgetTester tester) async {
when(cartBloc.cartRepository.addToCart(
cartId: cartData.id!,
quantity: 1,
variantId: cartData.lineItems!.physicalItems!.first.variantId!,
productId: cartData.lineItems!.physicalItems!.first.productId!,
)).thenAnswer((_) async => cartData);
when(sharedPreferenceService.setString('cartId', cartData.id!))
.thenAnswer((_) async => true);
await tester.pumpWidget(
_pumpTestWidgetWithEvent(
tester,
AddToCartEvent(
cartId: cartData.id!,
quantity: 1,
variantId: cartData.lineItems!.physicalItems!.first.variantId!,
productId: cartData.lineItems!.physicalItems!.first.productId!,
),
),
);
await tester.pumpAndSettle();
expect(find.text('Cart'), findsOneWidget);
expect(find.byType(ListView), findsOneWidget);
},
);
});
}
The test passes when I pass a new instance of cart bloc as shown below.
_pumpTestWidgetWithEvent(WidgetTester tester, CartEvent event) {
return MaterialApp(
home: BlocProvider(
create: (_) => CartBloc(
cartRepository: cartRepository,
sharedPreferenceService: sharedPreferenceService,
authBloc: authBloc,
)..add(event),
child: const CartView(),
),
);
}
Why is this happening?
I've also tried using BlocProvider.value and passing cartBloc as value. But this also failing.

Code doesn't even enter the onIceCandiate() while answering the SDP for webRTC in flutter

The code flow doesn't even enter the onIceCandidate function while answering the SDP for webRTC connection. The webRTC is used for Voice calling for VOIP in android and I have also setted up TURN server with viagene website.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'WebRTC lets learn together'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
CollectionReference firebaseInstance =
FirebaseFirestore.instance.collection("dmeet");
RTCPeerConnection _peerConnection;
MediaStream _localStream;
RTCVideoRenderer _remoteRenderer = RTCVideoRenderer();
var docId = TextEditingController();
var l;
var document;
_createOfferSdp() async {
RTCSessionDescription description =
await _peerConnection.createOffer({'offerToReceiveAudio': 1});
Map<String, dynamic> session = {"sdp": description.sdp};
document = firebaseInstance.doc();
document.collection("sdp").doc("offersdp").set(session);
await _peerConnection.setLocalDescription(description);
document.collection("icecandidate").snapshots().listen((result) async {
dynamic candidate = new RTCIceCandidate(
result['candidate'], result['sdpMid'], result['sdpMlineIndex']);
await _peerConnection.addCandidate(candidate);
});
print(session);
_peerConnection.onIceCandidate = (event) {
if (event.candidate != null) {
Map<String, dynamic> icecandidate = {
"candidate": event.candidate,
"sdpMid": event.sdpMid,
"sdpMlineIndex": event.sdpMlineIndex
};
document.collection("candidate").doc().set(icecandidate);
}
};
}
bool remotesaved = false;
_createAnswerSdp() async {
_peerConnection.onIceCandidate = (event) {
print("Candiate ${event.candidate}");
if (event.candidate != null) {
// Map<String, dynamic> icecandidate = {
// "candidate": event.candidate,
// "sdpMid": event.sdpMid,
// "sdpMlineIndex": event.sdpMlineIndex
// };
// document.collection("candidate").doc().set(icecandidate);
print("Candidate: ${event.candidate}");
}
};
firebaseInstance
.doc(docId.text)
.collection("sdp")
.doc("offersdp")
.get()
.then((value) async {
var remoteSession = value.data()["sdp"];
RTCSessionDescription description1 =
RTCSessionDescription(remoteSession, "offer");
await _peerConnection
.setRemoteDescription(description1)
.then((value) async {
RTCSessionDescription description2 =
await _peerConnection.createAnswer({'offerToReceiveAudio': 1});
Map<String, dynamic> session = {"sdp": description2.sdp};
firebaseInstance
.doc(docId.text)
.collection("sdp")
.doc("answersdp")
.set(session);
final iceCandidate = await firebaseInstance
.doc(docId.text)
.collection("candidate")
.get();
iceCandidate.docs.forEach((element) async {
print("Candidate ${element.data()["candidate"]}");
dynamic candidate = RTCIceCandidate(element.data()['candidate'],
element.data()['sdpMid'], element.data()['sdpMlineIndex']);
await _peerConnection.addCandidate(candidate);
});
});
});
}
showAlertDialog(BuildContext context) {
// set up the buttons
Widget cancelButton = FlatButton(
child: Text("Cancel"),
onPressed: () {},
);
Widget continueButton = FlatButton(
child: Text("Continue"),
onPressed: _createAnswerSdp,
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("AlertDialog"),
content: TextField(
controller: docId,
),
actions: [
cancelButton,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
initRenderer() async {
await _remoteRenderer.initialize();
}
#override
void initState() {
_createPeerConnection().then((pc) {
_peerConnection = pc;
});
initRenderer();
// _localStream.initialize();
super.initState();
}
#override
void dispose() {
_remoteRenderer.dispose();
super.dispose();
}
_getUserMedia() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': false,
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
// _localStream = stream;
// _peerConnection.addStream(stream);
return stream;
}
_createPeerConnection() async {
Map<String, dynamic> configuration = {
"iceServers": [
{"url": "stun:stun.l.google.com:19302"},
{
"url": "turn:numb.viagenie.ca",
"username": "******#gmail.com",
"credential": "*****",
}
]
};
final Map<String, dynamic> offerSdpConstraints = {
"mandatory": {
"OfferToReceiveAudio": true,
"OfferToReceiveVideo": false,
},
"optional": [],
};
_localStream = await _getUserMedia();
RTCPeerConnection pc =
await createPeerConnection(configuration, offerSdpConstraints);
pc.addStream(_localStream);
pc.onIceCandidate = (e) {
if (e.candidate != null) {
l = json.encode({
'candidate': e.candidate.toString(),
'sdpMid': e.sdpMid.toString(),
'sdpMlineIndex': e.sdpMlineIndex,
});
print("Her $l");
}
};
pc.onAddStream = (stream) {
print('addStream: ' + stream.id);
_remoteRenderer.srcObject = stream;
};
return pc;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: Row(
children: [
Flexible(child: RTCVideoView(_remoteRenderer)),
ElevatedButton(
child: Text("Create"),
onPressed: _createOfferSdp,
),
ElevatedButton(
onPressed: () {
showAlertDialog(context);
},
child: Text("Join"),
)
],
),
),
),
);
}
}
The Line that does not even entered is the function _createAnwerSdp() and next line to it!
The createAnswerSdp function is used for answering the call while getting the ice candidate.
What may be cause for the issue?
So, I can clearly see that there you haven't set any local description for the remote user who is going to answer this call.
_peerConnection.setLocalDescription(description2);
Hope this might help!

Flutter Cloud Firestore issue initializing futures

I am trying to get my first cloud firestore app working, using the firestore_flutter master example for cloud firestore as a template. In the example app main.dart has all the firestore and other init and access logic, and consequently runs the Futre void() main() method as in the example main.dart section copy below. It does not have the
====================
import...
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final FirebaseApp app = await FirebaseApp.configure(
name: 'test',
options: const FirebaseOptions(
googleAppID: '1:79601577497:ios:5f2bcc6ba8cecddd',
gcmSenderID: '79601577497',
apiKey: 'AIzaSyArgmRGfB5kiQT6CunAOmKRVKEsxKmy6YI-G72PVU',
projectID: 'flutter-firestore',
),
);
final Firestore firestore = Firestore(app: app);
runApp(MaterialApp(title: 'Firestore Example', home: MyHomePage(firestore: firestore)));
}
class MyHomePage extends StatelessWidget {
MyHomePage({this.firestore});
final Firestore firestore;
CollectionReference get messages => firestore.collection('messages');
====================
But in my app I want to load the data in the 3rd page after authenticating users with firebase_auth, but the main Future function never gets called (only 1 main right?)
Here is my code that does not work because main() never gets called. What do I need to do to make the code below work? PS: I tried making it a called function, moving it, but I am not able to make main run in my page source below.
It doesn't have the next line from teh example in it, because it didn't help either.
runApp(MaterialApp(title: 'Firestore Example', home: TopicList(firestore: firestore)));}
Thanks for any help in advance.
====================
import ...
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
final FirebaseApp app = await FirebaseApp.configure(
name: 'test',
options: const FirebaseOptions(
googleAppID: 'AIzaSyBPq8j3NT5DMmVgXLEN3Z91QJK32ZhrH90',
gcmSenderID: '36424891892',
apiKey: 'AIzaSyArgmRGfB5kiQT6CunAOmKRVKEsxKmy6YI',
projectID: 'usay-94b3a',
),
);
final Firestore firestore = Firestore(app: app);
}
class MessageList extends StatelessWidget {
MessageList({this.firestore});
final Firestore firestore;
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: firestore.collection("message").orderBy("timestamp", descending: true).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return const Text('Loading...');
final int messageCount = snapshot.data.documents.length;
return ListView.builder(
itemCount: messageCount,
itemBuilder: (_, int index) {
final DocumentSnapshot document = snapshot.data.documents[index];
final dynamic message = document['message'];
return ListTile(
trailing: IconButton(
onPressed: () => document.reference.delete(),
icon: Icon(Icons.delete),
),
title: Text(
message != null ? message.toString() : '<No message retrieved>',
),
subtitle: Text('Message ${index + 1} of $messageCount'),
);
},
);
},
);
}
}
class TopicList extends StatelessWidget {
TopicList({this.firestore});
final Firestore firestore;
CollectionReference get messages => firestore.collection('message');
Future<void> _addMessage() async {
await messages.add(<String, dynamic>{
'messageTitle': 'Hello world!',
'messageDescription': 'Hello world!',
'timestamp': FieldValue.serverTimestamp(),
});
}
Future<void> _runTransaction() async {
firestore.runTransaction((Transaction transaction) async {
final allDocs = await firestore.collection("message").getDocuments();
final toBeRetrieved = allDocs.documents.sublist(allDocs.documents.length ~/ 2);
final toBeDeleted = allDocs.documents.sublist(0, allDocs.documents.length ~/ 2);
await Future.forEach(toBeDeleted, (DocumentSnapshot snapshot) async {
await transaction.delete(snapshot.reference);
});
await Future.forEach(toBeRetrieved, (DocumentSnapshot snapshot) async {
await transaction.update(snapshot.reference, {
// "messageTitle": FieldValue.messageTitle,
// "messageDescription": FieldValue.messageDescription",
"timestamp": FieldValue.serverTimestamp()
});
});
});
await Future.forEach(List.generate(2, (index) => index), (item) async {
await firestore.runTransaction((Transaction transaction) async {
await Future.forEach(List.generate(10, (index) => index), (item) async {
await transaction.set(firestore.collection("message").document(), {
"messageTitle": "Created from Transaction $item",
"messageDescription": "Created from Transaction $item",
"timestamp": FieldValue.serverTimestamp()
});
});
});
});
}
Future<void> _runBatchWrite() async {
final batchWrite = firestore.batch();
final querySnapshot = await firestore.collection("message").orderBy("timestamp").limit(12).getDocuments();
querySnapshot.documents.sublist(0, querySnapshot.documents.length - 3).forEach((DocumentSnapshot doc) {
batchWrite.updateData(doc.reference, {
"messageTitle": "Batched messageTitle",
"messageDescription": "Batched messageDescription",
"timestamp": FieldValue.serverTimestamp()
});
});
batchWrite.setData(firestore.collection("message").document(), {
"messageTitle": "Batched message created",
"messageDescription": "Batched message created",
"timestamp": FieldValue.serverTimestamp()
});
batchWrite.delete(querySnapshot.documents[querySnapshot.documents.length - 2].reference);
batchWrite.delete(querySnapshot.documents.last.reference);
await batchWrite.commit();
}
#override
Widget build(BuildContext context) {
return FittedBox(
fit: BoxFit.scaleDown,
child: Scaffold(
appBar: AppBar(
title: const Text('Firestore Example'),
actions: <Widget>[
FlatButton(
onPressed: _runTransaction,
child: Text("Run Transaction"),
),
FlatButton(
onPressed: _runBatchWrite,
child: Text("Batch Write"),
)
],
),
body: MessageList(firestore: firestore),
floatingActionButton: FloatingActionButton(
onPressed: _addMessage,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
),
);
}
}
===============