Skip to content

Commit c559ed3

Browse files
authored
Merge pull request #189 from xmartlabs/ios-error-handling
[Chore] iOS error handling
2 parents 1683841 + ad3fa05 commit c559ed3

File tree

10 files changed

+119
-123
lines changed

10 files changed

+119
-123
lines changed

example/app/(app)/_layout.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import 'react-native-reanimated'
2+
3+
import { Stack } from 'expo-router'
4+
5+
import { useListenLocalStorage } from '@/common/localStorage'
6+
7+
export default function () {
8+
const [accessToken] = useListenLocalStorage('accessToken')
9+
10+
const isLoggedIn = Boolean(accessToken)
11+
12+
return (
13+
<Stack screenOptions={{ headerShown: false }}>
14+
<Stack.Protected guard={isLoggedIn}>
15+
<Stack.Screen name="index" />
16+
</Stack.Protected>
17+
<Stack.Protected guard={!isLoggedIn}>
18+
<Stack.Screen name="login" />
19+
</Stack.Protected>
20+
</Stack>
21+
)
22+
}

example/app/(app)/home.tsx renamed to example/app/(app)/index.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useRouter } from 'expo-router'
66
import { Fragment, useEffect, useState } from 'react'
77
import { Alert, Dimensions, Image, StyleSheet } from 'react-native'
88

9+
import { LineError } from '@/common/errors'
910
import {
1011
removeLocalStorageItem,
1112
setLocalStorageItem,
@@ -15,8 +16,11 @@ import { Bullet } from '@/components/Bullet'
1516
import { Button } from '@/components/Button'
1617
import { ThemedView } from '@/components/ThemedView'
1718

18-
function handleError(error: Error) {
19-
return Alert.alert(strings.errorTitle, error?.message ?? strings.errorMessage)
19+
function handleError(error: LineError) {
20+
const userInfo = JSON.parse(error.userInfo?.message ?? '')
21+
const title = strings.errorTitle + userInfo.statusCode
22+
const message = userInfo.message ?? strings.errorMessage
23+
return Alert.alert(title, message)
2024
}
2125

2226
export default function () {
@@ -41,7 +45,7 @@ export default function () {
4145
return Line.logout()
4246
.then(() => {
4347
removeLocalStorageItem('accessToken')
44-
router.replace('/login')
48+
router.replace('/')
4549
})
4650
.finally(() => setLoading(false))
4751
}
@@ -96,8 +100,8 @@ export default function () {
96100

97101
const strings = {
98102
accessToken: 'Access Token',
99-
errorMessage: 'Failed to get information',
100-
errorTitle: 'Error',
103+
errorMessage: 'The operation could not be completed',
104+
errorTitle: 'Error ',
101105
isFriend: 'Is Friend?',
102106
logOut: 'Logout',
103107
name: 'Name',

example/app/(app)/login.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export default function () {
2121
.then(result => {
2222
if (!result.accessToken.accessToken) return
2323
setLocalStorageItem('accessToken', result.accessToken.accessToken)
24-
router.replace('/home')
24+
router.replace('/')
2525
})
2626
.catch(() => {
2727
Alert.alert(strings.errorTitle, strings.errorMessage)

example/app/_layout.tsx

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,24 @@ import {
66
ThemeProvider,
77
} from '@react-navigation/native'
88
import Line from '@xmartlabs/react-native-line'
9-
import { useFonts } from 'expo-font'
10-
import { Stack } from 'expo-router'
11-
import * as SplashScreen from 'expo-splash-screen'
9+
import { Slot } from 'expo-router'
1210
import { StatusBar } from 'expo-status-bar'
13-
import { useEffect } from 'react'
11+
import { useLayoutEffect } from 'react'
1412

15-
import SpaceMono from '@/assets/fonts/SpaceMono-Regular.ttf'
13+
import { SplashScreen } from '@/components/SplashScreen'
1614
import { useColorScheme } from '@/hooks/useColorScheme'
1715

18-
SplashScreen.preventAutoHideAsync()
19-
2016
export default function () {
2117
const colorScheme = useColorScheme()
22-
const [loaded] = useFonts({ SpaceMono })
23-
24-
useEffect(() => {
25-
if (loaded) {
26-
Line.setup({ channelId: '2006826760' })
27-
SplashScreen.hideAsync()
28-
}
29-
}, [loaded])
3018

31-
if (!loaded) {
32-
return null
33-
}
19+
useLayoutEffect(() => {
20+
Line.setup({ channelId: '2006826760' })
21+
}, [])
3422

3523
return (
3624
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
37-
<Stack screenOptions={{ headerShown: false }}>
38-
<Stack.Screen name="(app)/login" />
39-
<Stack.Screen name="(app)/home" />
40-
<Stack.Screen name="+not-found" />
41-
</Stack>
25+
<SplashScreen />
26+
<Slot />
4227
<StatusBar style="auto" />
4328
</ThemeProvider>
4429
)

example/app/index.tsx

Lines changed: 0 additions & 13 deletions
This file was deleted.

example/common/errors.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export class LineError extends Error {
2+
public userInfo?: {
3+
message: string
4+
statusCode: number
5+
}
6+
7+
constructor(message: string) {
8+
super(message)
9+
}
10+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'react-native-reanimated'
2+
3+
import { useFonts } from 'expo-font'
4+
import * as ExpoSplashScreen from 'expo-splash-screen'
5+
import { FunctionComponent, useEffect } from 'react'
6+
7+
import SpaceMono from '@/assets/fonts/SpaceMono-Regular.ttf'
8+
9+
ExpoSplashScreen.preventAutoHideAsync()
10+
11+
export const SplashScreen: FunctionComponent = () => {
12+
const [loaded] = useFonts({ SpaceMono })
13+
14+
useEffect(() => {
15+
if (loaded) {
16+
ExpoSplashScreen.hideAsync()
17+
}
18+
}, [loaded])
19+
20+
return null
21+
}

example/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

example/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
},
4040
"devDependencies": {
4141
"@babel/core": "^7.28.0",
42-
"@types/react": "~19.1.8",
42+
"@types/react": "~19.0.10",
4343
"react-native-svg-transformer": "^1.5.1"
4444
},
4545
"private": true

ios/LineLoginModule.swift

Lines changed: 43 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22
import LineSDK
33

44
@objc(LineLogin) public class LineLogin: NSObject {
5-
5+
66
@objc public static func application(
77
_ application: UIApplication,
88
open url: URL,
@@ -21,27 +21,27 @@ import LineSDK
2121

2222
@objc func setup(_ arguments: NSDictionary, resolver resolve: @escaping RCTPromiseResolveBlock,
2323
rejecter reject: @escaping RCTPromiseRejectBlock) {
24-
25-
if LoginManager.shared.isSetupFinished {
26-
reject("SETUP_ALREADY_COMPLETED", "Setup has already been completed", nil)
27-
return
24+
25+
guard !LoginManager.shared.isSetupFinished else {
26+
reject("SETUP_ALREADY_COMPLETED", "Setup is already completed", nil)
27+
return
2828
}
29-
29+
3030
guard let channelID = arguments["channelId"] as? String else {
3131
reject("INVALID_ARGUMENTS", "Missing required argument: channelId", nil)
3232
return
3333
}
3434

3535
let universalLinkURL: URL? = (arguments["universalLinkUrl"] as? String).flatMap { URL(string: $0) }
36-
37-
return LoginManager.shared.setup(channelID: channelID, universalLinkURL: universalLinkURL)
36+
LoginManager.shared.setup(channelID: channelID, universalLinkURL: universalLinkURL)
37+
resolve(nil)
3838
}
3939

4040
@objc func login(_ arguments: NSDictionary?, resolver resolve: @escaping RCTPromiseResolveBlock,
4141
rejecter reject: @escaping RCTPromiseRejectBlock) {
4242

4343
guard let args = arguments else {
44-
LineLogin.nilArgument(reject)
44+
reject("INVALID_ARGUMENTS", "Expected argument is nil", NSError(domain: "", code: 200, userInfo: nil))
4545
return
4646
}
4747

@@ -84,12 +84,20 @@ import LineSDK
8484

8585
@objc func getCurrentAccessToken(_ resolve: @escaping RCTPromiseResolveBlock,
8686
rejecter reject: @escaping RCTPromiseRejectBlock) {
87-
if let token = AccessTokenStore.shared.current {
88-
resolve(self.parseAccessToken(token))
89-
}else{
90-
reject("Error getting access token",
91-
"There isn't an access token available",
92-
NSError(domain: "", code: 200, userInfo: nil))
87+
guard let token = AccessTokenStore.shared.current else {
88+
reject("ACCESS_TOKEN_NOT_AVAILABLE", "No access token is available", nil)
89+
return
90+
}
91+
resolve(self.parseAccessToken(token))
92+
}
93+
94+
@objc func getFriendshipStatus(_ resolve: @escaping RCTPromiseResolveBlock,
95+
rejecter reject: @escaping RCTPromiseRejectBlock) {
96+
API.getBotFriendshipStatus { result in
97+
switch result {
98+
case .success(let status): resolve(self.parseFriendshipStatus(status))
99+
case .failure(let error): error.rejecter(reject)
100+
}
93101
}
94102
}
95103

@@ -123,43 +131,26 @@ import LineSDK
123131
}
124132
}
125133

126-
@objc func getFriendshipStatus(_ resolve: @escaping RCTPromiseResolveBlock,
127-
rejecter reject: @escaping RCTPromiseRejectBlock) {
128-
API.getBotFriendshipStatus { result in
129-
switch result {
130-
case .success(let status): resolve(self.parseFriendshipStatus(status))
131-
case .failure(let error): error.rejecter(reject)
132-
}
133-
}
134-
}
135-
136-
static func nilArgument(_ reject: @escaping RCTPromiseRejectBlock) {
137-
return reject(
138-
"argument.nil",
139-
"Expect an argument when invoking method, but it is nil.",
140-
NSError(domain: "", code: 200, userInfo: nil))
134+
private func parseFriendshipStatus(_ status: GetBotFriendshipStatusRequest.Response) -> NSDictionary {
135+
return [
136+
"friendFlag": status.friendFlag
137+
]
141138
}
142139

143-
private func parseAccessToken(_ token: AccessToken) -> NSDictionary {
144-
var result = [
145-
"accessToken": token.value,
146-
"createdAt": token.createdAt,
147-
"expiresIn": token.expiresAt,
148-
] as [String : Any]
149-
150-
if let idToken = token.IDTokenRaw {
140+
private func parseAccessToken(_ accessToken: AccessToken) -> NSDictionary {
141+
var result: [String: Any] = [
142+
"accessToken": accessToken.value,
143+
"createdAt": accessToken.createdAt,
144+
"expiresIn": accessToken.expiresAt,
145+
]
146+
147+
if let idToken = accessToken.IDTokenRaw {
151148
result["idToken"] = idToken
152149
}
153-
150+
154151
return NSDictionary(dictionary: result)
155152
}
156153

157-
private func parseFriendshipStatus(_ status: GetBotFriendshipStatusRequest.Response) -> NSDictionary {
158-
return [
159-
"friendFlag": status.friendFlag
160-
]
161-
}
162-
163154
private func parseProfile(_ profile: UserProfile) -> NSDictionary {
164155
return [
165156
"displayName": profile.displayName,
@@ -179,43 +170,19 @@ import LineSDK
179170
]
180171
}
181172

182-
private func parseVerifyAccessToken(_ verification: AccessTokenVerifyResult) -> NSDictionary {
173+
private func parseVerifyAccessToken(_ accessToken: AccessTokenVerifyResult) -> NSDictionary {
183174
return [
184-
"channelId": verification.channelID,
185-
"expiresIn": verification.expiresIn,
186-
"scope": verification.permissions.map { $0.rawValue }.joined(separator: " ")
175+
"channelId": accessToken.channelID,
176+
"expiresIn": accessToken.expiresIn,
177+
"scope": accessToken.permissions.map { $0.rawValue }.joined(separator: " ")
187178
]
188179
}
189180
}
190181

191-
192-
extension Encodable {
193-
func toJSON() throws -> Any {
194-
let data = try JSONEncoder().encode(self)
195-
return try JSONSerialization.jsonObject(with: data, options: [])
196-
}
197-
func errorParsing(_ reject: @escaping RCTPromiseRejectBlock, _ name: String) {
198-
return reject(
199-
"error parsing",
200-
"There was an error when parsing `\(name)`",
201-
NSError(domain: "", code: 200, userInfo: nil))
202-
}
203-
204-
func resolver(_ resolve: @escaping RCTPromiseResolveBlock, _ reject: @escaping RCTPromiseRejectBlock, name: String) {
205-
do {
206-
let jsonValue = try self.toJSON()
207-
resolve(jsonValue)
208-
} catch {
209-
self.errorParsing(reject, name)
210-
}
211-
}
212-
}
213-
214182
extension LineSDKError {
215183
func rejecter(_ reject: @escaping RCTPromiseRejectBlock) {
216-
reject(
217-
"\(errorCode)",
218-
errorDescription,
219-
self)
184+
let code = String(errorCode)
185+
let message = errorDescription ?? "Unknown error"
186+
reject(code, message, self)
220187
}
221188
}

0 commit comments

Comments
 (0)