Firebase Dynamic Link pass custom parameters to iOS and android - firebase

I have a custom function in python to build the dynamic link:
def generate_dynamic_link(link, title=None, image=None, description=None, short=True, timeout=10):
api_url = FIREBASE_DYNAMIC_LINK_API_URL
domain = DYNAMIC_LINK_DOMAIN
apn = APP_APN
isi = APP_ISI
ibi = APP_IBI
payload = {
"dynamicLinkInfo": {
"domainUriPrefix": domain,
"link": link,
"androidInfo": {
"androidPackageName": apn,
},
"iosInfo": {
"iosBundleId": ibi,
"iosAppStoreId": isi
},
"socialMetaTagInfo": {
"socialTitle": title,
"socialDescription": description,
"socialImageLink": image
}
},
"suffix": {
"option": "SHORT" if short else "UNGUESSABLE"
}
response = requests.post(api_url, json=payload, timeout=timeout)
data = response.json()
if not response.ok:
raise Exception(data)
return data['shortLink']
I want to pass two parameters to the android and ios app. How can I Do that?
Example:
?type=user&username=testuser

I wrote my first Medium article about this (it’s not a great tutorial) but it shows how to do this. You are correct with how you pass data using ?yourDataHere at the end of your link.
https://augustkimo.medium.com/simple-flutter-sharing-and-deeplinking-open-apps-from-a-url-be56613bdbe6
Then you can handle the deep links by calling the function below. Pretty much you can get the link used to open the app, then get the data from that URL/link string
//ADD THIS FUNCTION TO HANDLE DEEP LINKS
Future<Null> initUniLinks()async{
try{
Uri initialLink = await getInitialUri();
print(initialLink);
var dataFromLink = initialLink.toString().split(‘?’)[1];
print(dataFromLink);
} on PlatformException {
print('platfrom exception unilink');
}
}

Related

iOS 14 Widgets + SwiftUI + Firebase?

I'm still pretty new to SwiftUI and Firebase. Recently, as a hobby, I have been developing an app for my school. After the launch of Xcode 12, I decided to experiment with the new features such as Widgets. However, since my app gets its data from Firebase, I've been having some problems. My most recent problem is this "Thread 1: "Failed to get FirebaseApp instance. Please call FirebaseApp.configure() before using Firestore". I'm not entirely sure where to put "FirebaseApp.configure()" as there is no AppDelegate.swift for the widget. My code is below.
Edit:
I've rearranged my code so that I am now getting the data from the original iOS app data model. I am therefore not importing Firebase within the widgets Swift file. However, I still get the same error ("SendProcessControlEvent:toPid: encountered an error: Error Domain=com.apple.dt.deviceprocesscontrolservice Code=8" and "-> 0x7fff5bb6933a <+10>: jae 0x7fff5bb69344 ; <+20> - Thread 1: "Failed to get FirebaseApp instance. Please call FirebaseApp.configure() before using Firestore""). I've also included #Wendy Liga's code, but I still got the same error. My newer code is below :
iOS App Data Model
import Foundation
import SwiftUI
import Firebase
import FirebaseFirestore
struct Assessment: Identifiable {
var id:String = UUID().uuidString
var Subject:String
var Class:Array<String>
var Day:Int
var Month:String
var Title:String
var Description:String
var Link:String
var Crit:Array<String>
}
class AssessmentsViewModel:ObservableObject {
#Published var books = [Assessment]()
private var db = Firestore.firestore()
// Add assessment variables
#Published var AssessmentSubject:String = ""
//#Published var AssessmentClass:Array<String> = [""]
#Published var AssessmentDay:Int = 1
#Published var AssessmentMonth:String = "Jan"
#Published var AssessmentTitle:String = ""
#Published var AssessmentDescription:String = ""
#Published var AssessmentLink:String = ""
#Published var AssessmentCrit:Array<String> = [""]
#Published var AssessmentDate:Date = Date()
func fetchData() {
db.collection("AssessmentsTest").order(by: "date").addSnapshotListener { (QuerySnapshot, error) in
guard let documents = QuerySnapshot?.documents else {
print("No documents")
return
}
self.books = documents.map { (QueryDocumentSnapshot) -> Assessment in
let data = QueryDocumentSnapshot.data()
let Subject = data["subject"] as? String ?? ""
let Class = data["class"] as? Array<String> ?? [""]
let Day = data["day"] as? Int ?? 0
let Month = data["month"] as? String ?? ""
let Title = data["title"] as? String ?? ""
let Description = data["description"] as? String ?? ""
let Link = data["link"] as? String ?? ""
let Crit = data["crit"] as? Array<String> ?? [""]
return Assessment(Subject: Subject, Class: Class, Day: Day, Month: Month, Title: Title, Description: Description, Link: Link, Crit: Crit)
}
}
}
func writeData() {
let DateConversion = DateFormatter()
DateConversion.dateFormat = "DD MMMM YYYY"
let Timestamp = DateConversion.date(from: "20 June 2020")
db.collection("AssessmentsTest").document(UUID().uuidString).setData([
"subject": AssessmentSubject,
"month": AssessmentMonth,
"day": AssessmentDay,
"title": AssessmentTitle,
"description": AssessmentDescription,
"link": AssessmentLink,
"crit": AssessmentCrit,
"date": AssessmentDate
]) { err in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
}
}
Widgets View
struct WidgetsMainView: View {
#ObservedObject private var viewModel = AssessmentsViewModel()
var body: some View {
HStack {
Spacer().frame(width: 10)
VStack(alignment: .leading) {
Spacer().frame(height: 10)
ForEach(self.viewModel.books) { Data in
HStack {
VStack {
Text(String(Data.Day))
.bold()
.font(.system(size: 25))
Text(Data.Month)
}
.padding(EdgeInsets(top: 16, leading: 17, bottom: 16, trailing: 17))
.background(Color(red: 114/255, green: 112/255, blue: 110/255))
.foregroundColor(Color.white)
.cornerRadius(10)
VStack(alignment: .leading, spacing: 0) {
Text("\(Data.Subject) Crit \(Data.Crit.joined(separator: " + "))")
.bold()
if Data.Title != "" {
Text(Data.Title)
} else {
Text(Data.Class.joined(separator: ", "))
}
}
.padding(.leading, 10)
}
}
.onAppear {
viewModel.books.prefix(2)
}
Spacer()
}
Spacer()
}
}
}
Widgets #main
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
#main
struct AssessmentsWidget: Widget {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
private let kind: String = "Assessments Widget"
public var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider(), placeholder: PlaceholderView()) { entry in
AssessmentsWidgetEntryView(entry: entry)
}
.configurationDisplayName("Assessments Widget")
.description("Keep track of your upcoming assessments.")
.supportedFamilies([.systemMedium])
}
}
Your main app needs to pass data to your extension, this can be achieved by allowing your app to use "App Groups" capability. What App Groups does is, it creates a container where your app can save data for you to share with your app extensions. So follow these steps to enable "App Groups".
1. Select your main App Target>Signing & Capabilities then tap + Capability and select "App Groups"
2. Tap on "+" to add a new container, and add a name to it after group. example : "group.com.widgetTest.widgetContainer"
Once you have created the "App Group" on your main app, you should take the same steps but on your "Widget Extension" target. This time, instead of creating a container, you should be able to select the container you already have from the main app. You can find a good video on YouTube explaining this process really well on here How to Share UserDefaults with app extensions
The next step I recommend is to create a Swift Package or a Framework, and add a new Model Object, this model object is the one you will be passing from your main app, to your widget extension. I chose a Swift Package.
To do this follow these steps:
1. File>New>Swift Package
A good video from the WWDC19 about this can be seen here
2. In your Swift Package, inside the "Sources" folder, Create a Custom Model which you will use in both your Main App, and Widget Extension
Make your object conform to "Codable" and that it is Public.
Important Make sure you import "Foundation" so that when you are decoding/encoding your object, it will do it properly.
3. Add your Package to your Main App and Widget Extension
Select your App's Target>General> Scroll to "Frameworks, Libraries, and Embedded Content"
Tap "+" and search for your Package
Do the same steps on your Widget's Extension
Now, all you need to do is "import" your module in the file that you will be creating your custom object in both your Main App, and on your WidgetExtension, then initialize your shared object on your main app and save it to UserDefaults by first encoding the object to JSON and then saving it to UserDefaults(suiteName: group.com.widgetTest.widgetContainer)
let mySharedObject = MySharedObject(name: "My Name", lastName: "My Last Name")
do {
let data = try JSONEncoder().encode(mySharedObject)
/// Make sure to use your "App Group" container suite name when saving and retrieving the object from UserDefaults
let container = UserDefaults(suiteName:"group.com.widgetTest.widgetContainer")
container?.setValue(data, forKey: "sharedObject")
/// Used to let the widget extension to reload the timeline
WidgetCenter.shared.reloadAllTimelines()
} catch {
print("Unable to encode WidgetDay: \(error.localizedDescription)")
}
Then in your widget extension, you want to retrieve your object from UserDefaults, decode it and you should be good to go.
Short Answer
Download your Firebase data, create a new object from that data, encode it to JSON, save it on your container by using UserDefaults, retrieve the object in your extension from the container, decode it and use it for your widget entry. Of course, all of this is assuming you follow the steps above.
I can confirm after testing that the following method works to use Firebase in the Widget Target without incorporating an app group, user defaults or anything else.
#main
struct FirebaseStartupSequence: Widget {
init() {
FirebaseApp.configure()
}
let kind: String = "FirebaseStartupSequence"
var body: some WidgetConfiguration {
IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
FirebaseStartupSequenceEntryView(entry: entry)
}
.configurationDisplayName("My Widget")
.description("This is an example widget.")
}
}
Simply use the init method in your widget to access a firebase instance.
This was the easiest solution for me as of today.
Taken from: https://github.com/firebase/firebase-ios-sdk/issues/6683
Additional Edit: Do you need to share authentication? No problem. Firebase has that covered here: https://firebase.google.com/docs/auth/ios/single-sign-on?authuser=1
You can add the appDelegate to your #main SwiftUI view
First create your appdelegate on your widget extension
import Firebase
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
look at #main, inside your widget extension,
#main
struct TestWidget: Widget {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
private let kind: String = "ExampleWidget"
public var body: some WidgetConfiguration {
...
}
}
#main is new swift 5.3 feature that allows value type entry point, so this is will be your main entry point for your widget extension
just add #UIApplciationDelegateAdaptor, inside your #main

How can you add a custom image to a notification in iOS with react-native-firebase

I want to use the data payload in a firebase cloud messaging to present an image in the notification. The image is specified as an url to a website where the image is hosted.
It appears as what I want to do is to add the image as an attachment, see row 8 below. There are however no image present, other than the application icon.
const notification = new firebase.notifications.Notification()
.setNotificationId("notification_id")
.setTitle(notification.data.title)
.setBody(notification.data.body)
.setData({ url: notification.data.url })
.ios.setLaunchImage(notification.data.icon)
.android.setBigPicture(notification.data.icon)
.ios.addAttachment("some_id", notification.data.icon, {});
The problem is that there are no error messages that can help me. The notification displays with the title and body as expected, but no image is present. From what I can read of the documentation what I want to do is possible.
The short answer is that react-native on iOS does not support "rich push notifications" i.e. notifications with images.
The longer answer is that it is rather simple to add support for an image to a react-native project if you add a little swift code.
Work around:
Open your xcode project and go to "editor"->"Add Target...". Select the "Application Extension" named "Notification Service Extension".
You can name it whatever you want but make sure that the correct project is selected if you are using CocoaPods.
Once it is created replace the content of override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) with:
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
// get the variables that is needed later.
guard let bestAttemptContent = bestAttemptContent,
let attachmentURLAsString = bestAttemptContent.userInfo["icon"] as? String,
// "icon" is the key for the image url in the notification. It
// could be named whatever you want.
let attachmentURL = URL(string: attachmentURLAsString) else {
return
}
// call a custom function to download the image before attaching
// it to the notification and presenting it.
downloadImageFrom(url: attachmentURL) { (attachment) in
if let attachment = attachment {
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
}
}
Then the downloadImageFrom function needs to be created:
private func downloadImageFrom(url: URL, with completionHandler: #escaping (UNNotificationAttachment?) -> Void) {
let task = URLSession.shared.downloadTask(with: url) { (downloadedUrl, response, error) in
//verify that a url exists.
guard let downloadedUrl = downloadedUrl else {
completionHandler(nil)
return
}
// create a local unique filepath.
var urlPath = URL(fileURLWithPath: NSTemporaryDirectory())
let uniqueURLEnding = ProcessInfo.processInfo.globallyUniqueString + ".png"
urlPath = urlPath.appendingPathComponent(uniqueURLEnding)
// fetch the image from the url
try? FileManager.default.moveItem(at: downloadedUrl, to: urlPath)
// if successful, return the image as an attachment.
do {
let attachment = try UNNotificationAttachment(identifier: "picture", url: urlPath, options: nil)
completionHandler(attachment)
} catch {
completionHandler(nil)
}
}
task.resume()
}
When you build the application it will use this code instead for loading notifications.
When sending the notification you have to remember to include the "icon" value. An example of what is needed to send a notification:
"notification": {
"body": "body",
"title": "title"
"mutable_content": true // this row is required for the notification to work!
},
"data": {
"icon":"https://pusher.com/static_logos/320x320.png", // change to your image url.
},

Why am I getting a 404 'App [my-project-id] was not found. The app preview may have expired.' trying push notifications on my Actions on Google?

I'm following the official instructions on how to send push notifications to users that gives their permission.
I'm able to follow all the instructions until this code
appMap.set('finish.push.setup', function(app)) {
if (app.isPermissionGranted()) {
const intent = app.getArgument('UPDATE_INTENT');
const userID = app.getArgument('UPDATES_USER_ID');
// code to save intent and userID in your db
app.tell("Ok, I'll start alerting you");
} else {
app.tell("Ok, I won't alert you");
}
}
the app.getArgument('UPDATE_INTENT') return undefined and checking the JSON it looks like it doesn't contain the intent at all but I have only one intent configured for updates so I hardcoded it's name in the code.
I got a userID and I hardcoded it too in the code.
Then I followed the instructions to get a service account key and I saved the JSON key locally.
Then the nasty problems begins.
I installed the required packages with npm install googleapis request --save and copied the code
const google = require('googleapis');
const key = require(PATH_TO_KEY);
let jwtClient = new google.auth.JWT(
key.client_email, null, key.private_key,
['https://www.googleapis.com/auth/actions.fulfillment.conversation'],
null
);
jwtClient.authorize(function (err, tokens) {
// code to retrieve target userId and intent
let notif = {
userNotification: {
title: '',
},
target: {
userId: '',
intent: ''
}
}
request.post('https://actions.googleapis.com/v2/conversations:send', {
'auth': {
'bearer': tokens.access_token
},
'json': true,
'body': { 'customPushMessage': notif }
}, function(err,httpResponse,body) {
console.log(httpResponse.statusCode + ': ' + httpResponse.statusMessage)
});
});
I edited it setting the right path to my key and edited the notification property with fixed values (the same title configured in the action, the userID returned by dialogflow and the name of my intent).
Then I noticed that the code is missing a const request = require('request'); and the line
let jwtClient = new google.auth.JWT(
gives an error so I changed to
let jwtClient = new google.google.auth.JWT(
I added a console.log('body', body); just to get more data and I got
body { error:
{ code: 404,
message: 'App [my-project-id] was not found. The app preview may have expired.',
status: 'NOT_FOUND' } }
Am I doing something wrong or the documentation has other errors I still have to catch?
try to add locale at target object:
let notif = {
userNotification: {
title: '',
},
target: {
userId: '',
intent: '',
locale: ''
}
}
For locale follow IETF BCP-47 language code as described here.
By default Google Actions use en-US language, and I figure out you are using a differente language code, so the system reply that cannot found us version of your application.

Making http Post request on Gupshup IDE works?

I copied gupshup's document code and modified just url as "http://posttestserver.com/post.php" and it doesn't work.
Anyone has an advice for me?
else if(event.message.toLowerCase() == "post") {
var contextParam = {
"User": {
"userName": "sbCobxxxx",
"Password": "xxxxxxx-9f-4307-9d9a-451f3xxxx075",
"Pin": "16776"
}
};
var url = "http://posttestserver.com/post.php";
var param = JSON.stringify(contextParam);
var header = {"Content-Type": "application/json"};
context.simplehttp.makePost(url, param, header);
}
function HttpResponseHandler(context, event) {
// if(event.geturl === "http://ip-api.com/json")
context.sendResponse(event.getresp);
}
Response returns empty string: ""
Thanks in advance.
Are you testing using Gupshup's emulator? If yes then POST and GET calls with headers and params doesn't work in the emulator as of now. The documentations mentions it. However, you can deploy the code and test it out using Gupshup proxy bot on Facebook messenger and it will work fine.
Here is a screenshot of the testing I did after directly copying your code into the IDE.

Trouble uploading videos from iOS using WordPress REST api

I am very new at this, so sorry if I have missed something.
I am trying to upload a video from iOS to WordPress using the WP REST API and Alamofire.
Some videos will upload just fine while other videos get a rest_upload_no_content_disposition status = 400 error.
Here is my code to uploading a video
class func pageVideo(accessToken:String, filePath:NSURL, completion: AnyObject? -> Void) {
let endpoint = "http://XXXXXXXXX.com/wp-json/wp/v2/media/?access_token=\(accessToken)"
let parameters = [
"Content-Type": "multipart/form-data",
"Content-Disposition": "attachment; filename=appVideo.mov",
"media_type": "file"
]
var fileData : NSData?
if let fileContents = NSFileManager.defaultManager().contentsAtPath(filePath.path!) {
fileData = fileContents
}
let mgr = Alamofire.Manager.sharedInstance
mgr.upload(.POST, endpoint, multipartFormData: { multipartFormData in
if let _fileData = fileData {
multipartFormData.appendBodyPart(data: _fileData, name: "file", fileName: "file.mov", mimeType: "file/mov")
}
for (key, value) in parameters {
multipartFormData.appendBodyPart(data: value.dataUsingEncoding(NSUTF8StringEncoding)!, name: key)
}
}, encodingCompletion: { encodingResult in
switch encodingResult {
case .Success(let upload, _, _):
upload.response {(request, response, data, error ) in
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
if let dict = json as? NSDictionary{
if let url = dict.valueForKeyPath("source_url") as? String{
completion(url)
}
}
} catch let error as NSError{
// completion(error.localizedDescription)
print(error.localizedDescription)
}
}
case .Failure(let encodingError):
print(encodingError)
}
})
}
}
It seems like shorter videos work while anything over 30 seconds fails.
Even shorter videos that do upload take a very long time.
Any help would be greatly appreciated.
Nothing was wrong with my code I just needed to compress the videos. the longer videos were hitting the WordPress upload limit. Also it's always a good idea to compress videos or images that your uploading somewhere.

Resources