Firebase Dynamic Link on getInitialLink in iOS (using Flutter) - ios

I have the following code in my application:
For creating the dynamic link I have:
/// Function that creates a DynamicLink for a [SingleFashionItem] using [FirebaseDynamicLinks]
static Future<String> createFashionItemLink(
BuildContext context,
SingleFashionItem fashionItem,
) async {
// Get the package information, this function won't probably fail,
// therefore don't any errors
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
final DynamicLinkParameters parameters = DynamicLinkParameters(
uriPrefix: CUSTOM_DOMAIN,
link: Uri.parse(
"$CUSTOM_DOMAIN/item?parentID=${fashionItem.parentID}&objectID=${fashionItem.objectID}"),
androidParameters: AndroidParameters(
packageName: packageInfo.packageName,
),
iosParameters: IosParameters(
bundleId: packageInfo.packageName,
appStoreId: "123456789" // TODO change this AppStoreID
),
socialMetaTagParameters: SocialMetaTagParameters(
title:
"${AppLocalizations.of(context).translate("check_out")} ${fashionItem.name}",
description: fashionItem.description,
imageUrl: Uri.parse(fashionItem.images[0].url),
),
);
// Build a short link
return parameters
.buildShortLink()
.then((shortDynamicLink) => shortDynamicLink.shortUrl.toString())
.catchError((error) {
print("The error is: ${error.toString()}");
return null;
});
}
// Function that handles the Dynamic Link using [FirebaseDynamicLinks]
static void handleDynamicLink(BuildContext context) async {
// Get the initial dynamic link if the app is started using the link
final PendingDynamicLinkData data =
await FirebaseDynamicLinks.instance.getInitialLink();
print("The dynamic data is ${data.toString()}");
// Handle the dynamic link
_handleDynamicLink(context, data);
// Handle when the application is brought to the foreground when being in background
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData data) async {
_handleDynamicLink(context, data);
}, onError: (OnLinkErrorException error) async {
// Send the error to Crashlytics
FirebaseCrashlytics.instance.reportNonFatalError(
exception: error,
customMessage: "Error while handling the dynamic link: ${data.link}",
userID: "not-available",
);
});
And for handling the dynamic link:
/// Function that handles the incoming [PendingDynamicLinkData]'s deep link
static void _handleDynamicLink(
BuildContext context, PendingDynamicLinkData data) {
final Uri deepLink = data?.link;
print("The deep link is: ${deepLink.toString()}");
// Check that the deep link is null or not
if (deepLink != null) {
// Handle deepLink
var isItem = deepLink.pathSegments.contains("item");
// Check if the items path segment is contained inside the path segment
if (isItem) {
// The deep link contains the items path segment
final objectID = deepLink.queryParameters["objectID"];
final parentID = deepLink.queryParameters["parentID"];
// Check that the itemID string is not null
if (objectID != null && parentID != null)
_goToDetailedFashionItemScreen(
context,
selectedFashionItemID: objectID,
parentID: parentID,
);
}
}
}
-> I've added in my
Associated Domains applinks:example.com
URL schemes com.example.application
In the Info.plist
<key>FirebaseDynamicLinksCustomDomains</key>
<array>
<string>https://example.com/item</string>
</array>
What I'm getting in a null value from the:
final Uri deepLink = data?.link
From what I've seen in this Github issue it might be because of a race condition, however this doesn't work for me.

This is the only solution that worked for me.
You should use the package "app_links" besides "firebase_dynamic_links".
Here is my code that worked for IOS:
static Future<void> initDynamicLinkIOS(BuildContext context) async {
final appLinks = AppLinks();
final Uri? uri = await appLinks.getInitialAppLink();
if (uri != null) {
final PendingDynamicLinkData? data = await FirebaseDynamicLinks.instance.getDynamicLink(uri);
Uri? deepLink = data?.link;
if (deepLink != null) {
// do your logic here, like navigation to a specific screen
}
}
}

Related

is it possible to tracing into the native when using dart to send a play next request to native ios using platform channel

I am using flutter to do a music player, fisrt I send the next song play request to native ios like this:
Future<void> skipToNext() async {
await _channel.invokeMethod("skipToNext");
}
but in the call back function, capture the parameter like this:
final serviceChannel = MethodChannel("tech.soit.quiet/background_callback");
final player = BackgroundMusicPlayer._internal(serviceChannel, MusicPlayer());
playQueueInterceptor?._player = player;
serviceChannel.setMethodCallHandler((call) async {
log.fine("background: ${call.method} args = ${call.arguments}");
switch (call.method) {
case 'loadImage':
if (imageLoadInterceptor != null) {
return await imageLoadInterceptor(MusicMetadata.fromMap(call.arguments));
}
throw MissingPluginException();
case 'getPlayUrl':
if (playUriInterceptor != null) {
final String? id = call.arguments['id'];
final String? fallbackUrl = call.arguments['url'];
return await playUriInterceptor(id, fallbackUrl);
}
throw MissingPluginException();
case "onPlayNextNoMoreMusic":
if (playQueueInterceptor != null) {
return (await playQueueInterceptor.onPlayNextNoMoreMusic(
createBackgroundQueue(call.arguments["queue"] as Map), PlayMode(call.arguments["playMode"] as int?)))
?.toMap();
}
throw MissingPluginException();
case "onPlayPreviousNoMoreMusic":
if (playQueueInterceptor != null) {
return (await playQueueInterceptor.onPlayPreviousNoMoreMusic(
createBackgroundQueue(call.arguments["queue"] as Map),
PlayMode(call.arguments["playMode"] as int?),
))
?.toMap();
}
throw MissingPluginException();
default:
throw MissingPluginException("can not hanle : ${call.method} ");
}
the call back code could get the id from parameter but I did not pass the id into native? why the parameter contains value? is it possible to tracing into the ios the check where get the arguments?

Flutter web: Values retrieved from Firestore map are truncated when added to List

In my Flutter Web application I am retrieving values from the map timeslots in Firestore.
This is what the data looks like:
But, instead of retrieving the whole list of values, I get a truncated list like this:
[Mo-Washing-(09:00-10:00, 10:00-11:00, 11:00-12:00, ..., 20:00-21:00, 21:00-22:00)]
Below I have included the 2 functions responsible for retrieving the data and adding it to the list object
static List object = [];
static Map<String, dynamic> timeDetails = {};
static Map<String, dynamic> userDetails = {};
checkExists(docuID) async {
return await firestore()
.collection('environments')
.doc(docuID)
.get()
.then((val) {
userDetails.addAll(val.data());
}).whenComplete(() async {
fs.DocumentSnapshot snapShot = await firestore()
.collection('environments')
.doc(docuID)
.collection('Washing')
.doc('monday')
.get();
if (snapShot == null || !snapShot.exists) {
print('does not exist');
} else {
await getData(docuID, 'Washing');
}
setState(() {});
});
}
getData(docuID, machineName) async {
return await firestore()
.collection('environments')
.doc(docuID)
.collection(machineName)
.doc('monday')
.get()
.then((val) {
timeDetails.addAll(val.data());
}).whenComplete(() {
object.add('Mo-$machineName-${timeDetails['timeslots'].values}');
print(object);
setState(() {});
});
}
This also happens in debugPrint. Would anyone know why this is happening and how I could solve it? Any help on this would be appreciated!
Neither the workaround as mentioned on Github nor debugPrint worked for me, but I managed to solve this by adding .toList() to my getData function:
getData(docuID, machineName) async {
return await firestore()
.collection('environments')
.doc(docuID)
.collection(machineName)
.doc('monday')
.get()
.then((val) {
timeDetails.addAll(val.data());
}).whenComplete(() {
//toList() is added here to .add
object.add('Mo-$machineName-${timeDetails['timeslots'].values.toList()}');
print(object);
setState(() {});
});
}
Output:
[Mo-Washing-[09:00-10:00, 10:00-11:00, 11:00-12:00, 12:00-13:00, 13:00-14:00, 14:00-15:00, 15:00-16:00, 16:00-17:00, 17:00-18:00, 18:00-19:00, 19:00-20:00, 20:00-21:00, 21:00-22:00]

Flutter : Capture the Future response from a http call as a normal List

I am trying to make my previously static app, dynamic by making calls to the backend server.
This is what my service looks like
String _url = "http://localhost:19013/template/listAll";
Future<List<TemplateInfoModel>> fetchTemplates() async {
final response =
await http.get(_url);
print(response.statusCode);
if (response.statusCode == 200) {
// If the call to the server was successful, parse the JSON
Iterable l = json.decode(response.body);
List<TemplateInfoModel> templates = l.map((i) => TemplateInfoModel.fromJson(i)).toList();
return templates;
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
And my model looks like this :
class TemplateInfoModel {
final String templateId;
final String messageTag;
final String message;
final String messageType;
TemplateInfoModel({this.templateId, this.messageTag, this.message,
this.messageType});
factory TemplateInfoModel.fromJson(Map<String, dynamic> json) {
return TemplateInfoModel ( templateId: json['templateId'], messageTag : json['messsageTag'], message : json ['message'] , messageType : json['messageType']);
}
}
I have a utils method within which I am capturing the http request/response data; which would then use that to create a DropDown Widget ( or display it within a text)
My earlier dummy data was a list; I am wondering how best I can convert this Future> to List
class SMSTemplatingEngine {
var _SMSTemplate; //TODO this becomes a Future<List<TemplateInfoModels>>
// TemplateInfoService _templateInfoService;
SMSTemplatingEngine(){
_SMSTemplate=fetchTemplates();
}
// var _SMSTemplate = {
// 'Reminder':
// 'Reminder : We’re excited to launch a new feature on our platform that will revolutionize your Facebook marketing and triple your ROI. Visit url.com to learn more',
// 'New Message':
// 'New Message: We’re excited to launch a new feature on our platform that will revolutionize your Facebook marketing and triple your ROI. Visit url.com to learn more',
// 'Connecting Again':
// 'Connecting Again : We’re excited to launch a new feature on our platform that will revolutionize your Facebook marketing and triple your ROI. Visit url.com to learn more',
// };
List<String> getKeys(){
List<String> smsKeys = new List();
for ( var key in _SMSTemplate.keys)
smsKeys.add(key);
return smsKeys;
}
String getValuePerKey(String key){
return _SMSTemplate['${key}'];
}
}
P.S. I have looked at some posts but I was completely bowled over since I am a Flutter Newbie.
Is there an easier way for this to happen.
Thanks,
The widget which would display the content from the http call
var templateMessagesDropDown = new DropdownButton<String>(
onChanged: (String newValue) {
setState(() {
templateMsgKey = newValue;
print("Selcted : ${templateMsgKey.toString()} ");
});
},
// value: _defaultTemplateValue,
style: textStyle,
//elevation: 1,
hint: Text("Please choose a template"),
isExpanded: true,
//
items: smsEngine.getKeys().map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
);
I am wondering how best I can convert this Future to List
Future<List<TemplateInfoModel>> fetchTemplates() should return a List that you're expecting. You may want to consider either using FutureBuilder or StreamBuilder to update the UI elements on your screen. Or if you're not keen on using either of those, you can just call the Future and update the List on your current screen.
List<TemplateInfoModel> _listTemplateInfoModel = [];
...
fetchTemplates().then((value){
setState((){
_listTemplateInfoModel = value;
});
});

Save data to localStorage from api

I want to save data on Local Storage to make app works offline.
I want to get data from api, json data and save them to local storage,
PostList and Post are my classes
Future <PostsList> fetchPostsList(language) async {
final response =
await http.get('https://sas-survey.urbanway.net/api/questions/1/${language}');
if(response.statusCode == 200){
// storagepost.setItem('resultPost', response.body);
return PostsList.fromJson(json.decode(response.body));
}else{
// return storagepost.getItem('resultPost');
throw Exception('Filed to load post');
}
}
final LocalStorage storagee = new LocalStorage('result_app');
static PostsList resultsListEn = new PostsList();
_addItemEn( String id,
String question,
String photo,
List answers,
String categoryid) {
setState(() {
final resulten = new Post(id: id, title: question, image: photo, answers: answers, category: categoryid);
print(resulten.title.toString());
resultsListEn.posts.add(resulten);
_saveToStorageEn();
});
}
_saveToStorageEn() {
widget.storagee.setItem('en', resultsListEn.toJsonEncodable());
}
new FutureBuilder<PostsList>(
future: fetchPostsList(en),
builder: (context, snapshot){
if(snapshot.hasData){
List<Post> questionsEn =snapshot.data.posts;
questionsEn.forEach((question){
_addItemEn(question.id, question.title, question.image, question.answers, question.category);
});
return Center();
}else if(snapshot.hasError){
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
this is my code what im missing?
You can use Shared Preferences for basic data. Add a toJson and fromJson method to your models PostList and Post and serialize it using json.decode/encode and save it as a string. When reading it back convert it back and use it. Examples of how I did it below. Your code is not complete enough to make adjustments to.
get postValue {
var jsonContent = sharedPreferencesInstance.getString('post-key');
if(jsonContent != null) {
return Post.fromJson(json.decode(jsonContent));
}
return null;
}
set postValue(Post post) => sharedPreferencesInstance.setString(
'post-key', json.encode(post.toJson()));

Why is Xamarin.Auth throwing authentication error with OAuth1Authenticator and Twitter

I am currently using Xamarin.Auth on a iOS project to handle some user authentication via Facebook and Twitter in my application. The Facebook authentication using OAuth2Authenticator works great and my implementation was based mainly off the docs (http://components.xamarin.com/gettingstarted/xamarin.auth). Twitter however still uses OAuth1 it seems and thus I based my implementation mainly off the answer in this StackOverflow questions (https://stackoverflow.com/a/21982205). Everything works properly and I am able to retrieve user, tweets, etc. but after all the code executes I receive a "Authentication Error" popup on the screen saying "Object reference not set to an instance of an object." there is nothing printed to the console however as is the case with most normal errors I have seen thus far. I can dismiss the popup and everything continues to preform correctly. I believe I have narrowed the problem down to something within the OAuth1Authenticator request as I still receive the error when all of the other handling code has been commented out. Please reference the code below to see what might be the cause of this.
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
signupBtn.TouchUpInside += delegate {
LoginToTwitter(true, this);
};
}
void LoginToTwitter(bool allowCancel, UIViewController _vc)
{
var auth = new OAuth1Authenticator (
consumerKey: "My Consumer Key",
consumerSecret: "My Consumer Secret",
requestTokenUrl: new Uri("https://api.twitter.com/oauth/request_token"),
authorizeUrl: new Uri("https://api.twitter.com/oauth/authorize"),
accessTokenUrl: new Uri("https://api.twitter.com/oauth/access_token"),
callbackUrl: new Uri("My callback url"),
getUsernameAsync: (IDictionary<string, string> accountProperties) => {
string screen_name = "";
if (accountProperties.TryGetValue("screen_name", out screen_name)) {
Console.WriteLine("SN: {0}", screen_name);
Account a = new Account(screen_name, accountProperties);
AuthenticatorCompletedEventArgs e = new AuthenticatorCompletedEventArgs(a);
TwitterCompleted(e, _vc);
}
return null;}
);
auth.AllowCancel = allowCancel;
UIViewController authView = auth.GetUI ();
_vc.PresentViewController (authView, true, null);
}
void TwitterCompleted (AuthenticatorCompletedEventArgs e, UIViewController _vc)
{
var theAccount = e.Account;
var theProperties = theAccount.Properties;
foreach (var item in theProperties) {
Console.WriteLine (item); //debugging
}
InvokeOnMainThread (delegate {
_vc.DismissViewController (true, null);
});
AccountStore.Create ().Save (e.Account, "Twitter");
if (!e.IsAuthenticated) {
Console.WriteLine("Not authorized");
return;
}
theScreenName = e.Account.Properties["screen_name"];
theCount = "2";
IDictionary<string, string> theDict = new Dictionary<string, string>();;
theDict.Add("screen_name", theScreenName);
theDict.Add("count", theCount);
var request = new OAuth1Request("GET", new Uri("https://api.twitter.com/1.1/statuses/user_timeline.json"), theDict, e.Account, false);
request.GetResponseAsync().ContinueWith (t => {
if (t.IsFaulted)
Console.WriteLine("Error: {0}", t.Exception.InnerException.Message);
else if (t.IsCanceled)
Console.WriteLine("Canceled");
else
{
var obj = JsonValue.Parse (t.Result.GetResponseText());
Console.WriteLine("object: {0}", obj); // debugging
}
}, uiScheduler);
return;
}
private readonly TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
instead of returning null in "getUsernameAsync" return Task

Resources