Background
In the previous LINE Bot Smart Business Card Manager project, we have already implemented the function of automatically recognizing business cards using the Gemini Pro Vision API. Users only need to take a photo of the business card and upload it, and the AI can automatically parse information such as name, title, company, phone number, and email, and store it in the Firebase Realtime Database.
But in actual use, I found a pain point:
📱 I already have digitized business card data, but I still have to manually enter each field to add it to my phone's address book...
Imagine these scenarios:
- 📇 Attending a seminar: Collected 20 business cards, and still have to manually add them to the address book after recognition is complete
- 💼 Business visit: Got a customer's business card and wanted to quickly add it to the phone contacts
- 🤝 Social occasions: Met new friends and wanted to save their contact information immediately
So I thought: Since the data is already digitized, why can't I add it to the address book with one click?
The most ideal way is: Generate a vCard QR Code, and let users scan it to directly add it to their address book!
Project Code
https://github.com/kkdai/linebot-namecard-python
(Through this code, you can quickly deploy to GCP Cloud Run and enjoy the convenience of serverless)
📚 About vCard and QR Code
Introduction to vCard Format
vCard (Virtual Contact File) is a standard format for electronic business cards, with the file extension .vcf. Almost all smartphones and mail clients natively support vCard, including:
- 📱 iPhone: Automatically identifies and prompts "Add Contact"
- 🤖 Android: Import via the Contacts App
- 💻 Computer: Outlook, Apple Mail, Gmail, etc. all support
vCard 3.0 Format Example
BEGIN:VCARD
VERSION:3.0
FN:Kevin Dai
N:Dai;Kevin;;;
ORG:LINE Taiwan
TITLE:Software Engineer
TEL;TYPE=WORK,VOICE:+886-123-456-789
EMAIL;TYPE=WORK:kevin@example.com
ADR;TYPE=WORK:;;Taipei, Taiwan;;;;
NOTE:Met at DevFest 2025
END:VCARD
Advantages of QR Code + vCard
Encoding vCard into a QR Code has several advantages:
- ✅ Scan and Add: Automatically recognized after scanning with the camera App
- ✅ Cross-platform: Both iPhone/Android are supported
- ✅ No download required: No need to save files and then import
- ✅ Complete data: Contains all contact information and notes
✨ Project Feature Introduction
Core Function Flow
User uploads business card image
↓
Gemini Vision API recognition
↓
Stored in Firebase Realtime Database
↓
Displays business card Flex Message
↓
【New Feature】Click the "📥 Add to Address Book" button
↓
Generate vCard QR Code
↓
Upload to Firebase Storage
↓
Returns QR Code image to the user
↓
User scans → Add to address book ✅
New Feature Highlights
-
📥 One-click generate QR Code
- Click the "Add to Address Book" button on the business card
- Automatically generate a vCard QR Code containing complete information
- Includes name, title, company, phone number, email, address, and notes
-
☁️ Firebase Storage Integration
- QR Code images are uploaded to Firebase Storage
- Automatically set to public read
- Sent to users via LINE ImageMessage
-
🤖 Gemini Vision Collaboration
- Original business card recognition function (Gemini Vision API)
- Recognition results → Firebase Database → QR Code
- Complete integration of AI recognition + cloud storage + mobile applications
-
📱 User-friendly
- Automatically generate usage instructions
- Supports iPhone/Android
- Scan to add to address book
💻 Core Function Implementation
1. vCard Format Generation
First, implement the generation of the vCard format string, which is the foundation of the entire function.
File location: app/qrcode_utils.py
def generate_vcard_string(namecard_data: Dict[str, str]) -> str:
"""
Generate vCard 3.0 format string from namecard data.
Args:
namecard_data: Dictionary containing namecard fields
Returns:
vCard formatted string
"""
name = namecard_data.get('name', '')
title = namecard_data.get('title', '')
company = namecard_data.get('company', '')
phone = namecard_data.get('phone', '')
email = namecard_data.get('email', '')
address = namecard_data.get('address', '')
memo = namecard_data.get('memo', '')
# Build vCard 3.0 format
vcard_lines = [
'BEGIN:VCARD',
'VERSION:3.0',
f'FN:{name}',
f'N:{name};;;', # Family Name; Given Name; Additional Names; Honorific Prefixes; Honorific Suffixes
]
if company:
vcard_lines.append(f'ORG:{company}')
if title:
vcard_lines.append(f'TITLE:{title}')
if phone:
# Clean phone number format for vCard
clean_phone = phone.replace('-', '').replace(' ', '')
vcard_lines.append(f'TEL;TYPE=WORK,VOICE:{clean_phone}')
if email:
vcard_lines.append(f'EMAIL;TYPE=WORK:{email}')
if address:
# vCard address format: PO Box;Extended Address;Street;City;Region;Postal Code;Country
vcard_lines.append(f'ADR;TYPE=WORK:;;{address};;;;')
if memo:
# Escape special characters in memo
escaped_memo = memo.replace('\n', '\\n').replace(',', '\\,').replace(';', '\\;')
vcard_lines.append(f'NOTE:{escaped_memo}')
vcard_lines.append('END:VCARD')
return '\n'.join(vcard_lines)
Design points:
- ✅ Use vCard 3.0 format (best compatibility)
- ✅ Handle empty fields: Only add corresponding fields when there is data
- ✅ Phone number cleaning: Remove
-and spaces to ensure the correct format - ✅ Special character escaping: Line breaks, commas, and semicolons in the notes need to be escaped
- ✅ Complete information: Includes the note field, retaining additional information from AI recognition
2. QR Code Image Generation
Use the qrcode package to encode the vCard string into a QR Code image.
def generate_vcard_qrcode(namecard_data: Dict[str, str],
box_size: int = 10,
border: int = 2) -> BytesIO:
"""
Generate QR Code image containing vCard data.
Args:
namecard_data: Dictionary containing namecard fields
box_size: Size of each box in pixels (default: 10)
border: Border size in boxes (default: 2)
Returns:
BytesIO object containing PNG image data
"""
# Generate vCard string
vcard_string = generate_vcard_string(namecard_data)
# Create QR Code instance
qr = qrcode.QRCode(
version=None, # Auto-determine version based on data size
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=box_size,
border=border,
)
# Add vCard data
qr.add_data(vcard_string)
qr.make(fit=True)
# Generate image
img = qr.make_image(fill_color="black", back_color="white")
# Save to BytesIO
img_bytes = BytesIO()
img.save(img_bytes, format='PNG')
img_bytes.seek(0) # Reset pointer to beginning
return img_bytes
Key parameter descriptions:
| Parameter | Description | Reason for selection |
|---|---|---|
version=None |
Automatically determines the QR Code size | Automatically adjusts based on data volume to ensure it can be scanned |
error_correction=L |
Error correction level (Low) | vCard data will not be frequently damaged, choose the minimum level to reduce the QR Code size |
box_size=10 |
Size of each box in pixels (default: 10) | Good scanability on the phone screen |
border=2 |
Border size in boxes (default: 2) | Meets the minimum border of the QR Code standard |
Why use BytesIO?
- ✅ No need to write to the physical file system
- ✅ Directly process images in memory
- ✅ Convenient for subsequent uploading to Firebase Storage
- ✅ Reduce I/O operations and improve performance
3. Firebase Storage Integration
This is the core of the entire function: uploading the QR Code image to Firebase Storage and obtaining the public URL.
File location: app/firebase_utils.py
from firebase_admin import storage
from io import BytesIO
def upload_qrcode_to_storage(
image_bytes: BytesIO, user_id: str, card_id: str) -> str:
"""
Upload QR Code image to Firebase Storage and return the public URL
Args:
image_bytes: BytesIO object of the QR Code image
user_id: User ID
card_id: Business card ID
Returns:
Public URL of the image, returns None if it fails
"""
try:
bucket = storage.bucket()
blob_name = f"qrcodes/{user_id}/{card_id}.png"
blob = bucket.blob(blob_name)
# Upload image
image_bytes.seek(0) # Reset pointer to the beginning
blob.upload_from_file(image_bytes, content_type='image/png')
# Set to public read
blob.make_public()
# Return public URL
return blob.public_url
except Exception as e:
print(f"Error uploading QR code to storage: {e}")
return None
Design considerations:
-
File path structure:
qrcodes/{user_id}/{card_id}.png- Categorized by user for easy management
- Use card_id to ensure the file name is unique
- Repeated generation of the same business card will overwrite the old file
-
Public permissions:
blob.make_public()- The QR Code needs to be accessed by the LINE Bot via URL
- Firebase Storage Rules are set to
allow read: if true - Write permissions are only given to the Admin SDK (Cloud Run)
-
Content-Type setting:
content_type='image/png'- Ensure the browser displays the image correctly
- LINE ImageMessage requires the correct MIME type
4. Firebase Initialization Configuration
In app/main.py, correctly set up the Firebase Storage Bucket:
import firebase_admin
from firebase_admin import credentials
# Firebase initialization
firebase_config = {
"databaseURL": config.FIREBASE_URL,
}
# If Storage Bucket is set, add the configuration
if config.FIREBASE_STORAGE_BUCKET:
firebase_config["storageBucket"] = config.FIREBASE_STORAGE_BUCKET
try:
cred = credentials.ApplicationDefault()
firebase_admin.initialize_app(cred, firebase_config)
print("Firebase Admin SDK initialized successfully.")
except Exception as e:
# Parse JSON from environment variables
gac_str = os.environ.get("GOOGLE_APPLICATION_CREDENTIALS_JSON")
if gac_str:
cred_json = json.loads(gac_str)
cred = credentials.Certificate(cred_json)
firebase_admin.initialize_app(cred, firebase_config)
print("Firebase Admin SDK initialized successfully from ENV VAR.")
Environment variable settings:
# Need to be set when deploying to Cloud Run
FIREBASE_STORAGE_BUCKET=your-project-id.firebasestorage.app
# Or the old format
FIREBASE_STORAGE_BUCKET=your-project-id.appspot.com
Why do you need to explicitly set the Storage Bucket?
- Firebase Admin SDK defaults to only initializing the Database
- If
storageBucketis not specified, callingstorage.bucket()will fail - Explicit setting can avoid runtime errors
5. LINE Bot Postback Handling
When a user clicks the "Add to Address Book" button, handle the complete process.
File location: app/line_handlers.py
from linebot.models import ImageSendMessage, TextSendMessage
async def handle_download_contact(
event: PostbackEvent, user_id: str, card_id: str, card_name: str):
"""Handle the request to download the contact QR Code"""
try:
# 1. Get the complete business card data from Firebase
card_data = firebase_utils.get_card_by_id(user_id, card_id)
if not card_data:
await line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text='Could not find the business card data.'))
return
# 2. Generate vCard QR Code
qrcode_image = qrcode_utils.generate_vcard_qrcode(card_data)
# 3. Upload to Firebase Storage and get the URL
image_url = firebase_utils.upload_qrcode_to_storage(
qrcode_image, user_id, card_id)
if not image_url:
await line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text='An error occurred while generating the QR Code, please try again later.'))
return
# 4. Generate usage instructions
instruction_text = qrcode_utils.get_qrcode_usage_instruction(card_name)
# 5. Return the QR Code image and usage instructions
image_message = ImageSendMessage(
original_content_url=image_url,
preview_image_url=image_url
)
text_message = TextSendMessage(text=instruction_text)
await line_bot_api.reply_message(
event.reply_token,
[image_message, text_message])
except Exception as e:
print(f"Error in handle_download_contact: {e}")
await line_bot_api.reply_message(
event.reply_token,
TextSendMessage(text='An error occurred while processing your request, please try again later.'))
Flow design highlights:
- ✅ Complete error handling: Error checking for each step
- ✅ Friendly prompts: Clear error messages when it fails
- ✅ Return two messages at once: Image + instruction text
- ✅ Asynchronous processing: Use
async/awaitto avoid blocking
6. Flex Message Button Configuration
Add an "Add to Address Book" button to the business card's Flex Message.
File location: app/flex_messages.py
"footer": {
"type": "box",
"layout": "vertical",
"spacing": "sm",
"contents": [
{
"type": "box",
"layout": "horizontal",
"spacing": "sm",
"contents": [
{
"type": "button",
"style": "link",
"height": "sm",
"action": {
"type": "postback",
"label": "Add/Modify Memo",
"data": f"action=add_memo&card_id={card_id}",
"displayText": f"I want to add a memo for {name}"
},
"flex": 1
},
{
"type": "button",
"style": "link",
"height": "sm",
"action": {
"type": "postback",
"label": "Edit Data",
"data": f"action=edit_card&card_id={card_id}",
"displayText": f"I want to edit {name}'s business card"
},
"flex": 1
}
]
},
{
"type": "button",
"style": "primary",
"height": "sm",
"action": {
"type": "postback",
"label": "📥 Add to Address Book",
"data": f"action=download_contact&card_id={card_id}",
"displayText": f"Download {name}'s contact information"
},
"margin": "sm"
}
]
}
UI design considerations:
┌────────────────────────────────────┐
│ [Add/Modify Memo] [Edit Data] │ ← First row side by side (link style)
│ [📥 Add to Address Book] │ ← Second row independent (primary style)
└────────────────────────────────────┘
-
First row side by side: Commonly used editing functions, use
linkstyle -
Second row independent: Download function, use
primarystyle to highlight - Emoji visualization: 📥 icon to let users visually identify the download function
7. Usage Instruction Generation
Provide clear usage instructions so that users know how to use the QR Code.
def get_qrcode_usage_instruction(name: str) -> str:
"""
Get user instruction message for using the QR Code.
Args:
name: Name of the person on the namecard
Returns:
Instruction message string
"""
return f"""Generated contact QR Code for "{name}"!
📱 How to use:
1. Scan the QR Code above with your phone's camera App
2. The system will automatically recognize contact information
3. Click "Add Contact" to import
✅ Supports all smartphones with iPhone and Android"""
Design philosophy:
- ✅ Personalized message: Includes the name of the business card owner
- ✅ Clear steps: 1-2-3 simple and clear
- ✅ Cross-platform instructions: Emphasizes that iPhone/Android are supported
- ✅ Emoji visualization: 📱 and ✅ make the message more friendly
🤖 Gemini Vision API's Role in the Overall Architecture
Although this time the QR Code function itself does not use Gemini, the entire business card manager system is a complete application centered on the Gemini Vision API.
Gemini + Firebase Storage Collaboration Flow
┌─────────────────────────────────────────────┐
│ User uploads business card photo │
└──────────────┬──────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ LINE Bot receives image │
│ (app/line_handlers.py) │
└──────────────┬──────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Gemini Pro Vision API recognition │
│ - Name, title, company │
│ - Phone, Email, Address │
│ (app/gemini_utils.py) │
└──────────────┬──────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Stored in Firebase Realtime Database │
│ /namecard/{user_id}/{card_id}/ │
└──────────────┬──────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ User clicks "📥 Add to Address Book" │
└──────────────┬──────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Generate vCard QR Code │
│ (app/qrcode_utils.py) │
└──────────────┬──────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Upload to Firebase Storage │
│ qrcodes/{user_id}/{card_id}.png │
│ (app/firebase_utils.py) │
└──────────────┬──────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ Returns QR Code to the user │
│ User scans → Add to address book ✅ │
└─────────────────────────────────────────────┘
Key Role of Gemini Vision API
In app/gemini_utils.py, we use Gemini Pro Vision to parse business card images:
def generate_json_from_image(img: PIL.Image, prompt: str):
"""
Use Gemini Pro Vision to extract structured data from image.
"""
model = genai.GenerativeModel('gemini-1.5-pro')
response = model.generate_content([prompt, img])
return response
Prompt Design (app/config.py):
IMGAGE_PROMPT = """
This is a business card, you are a business card secretary. Please organize the following information into json for me.
If you can't see it, please fill in N/A
Just json:
name, title, address, email, phone, company.
Where the content format of phone is #886-0123-456-789,1234. If there is no extension, ignore ,1234
"""
Why choose Gemini Vision?
- ✅ Strong Chinese recognition ability: Taiwanese business cards often have Chinese, and Gemini handles the effect well
- ✅ Structured output: Directly generate JSON format, easy to parse
- ✅ Fault tolerance: Automatically fills in "N/A" when it cannot be recognized
- ✅ Flexible format: Supports various business card layouts
Firebase Dual Service Integration
This project uses two major services of Firebase at the same time:
| Service | Purpose | Data type | Access method |
|---|---|---|---|
| Realtime Database | Store structured business card data | JSON | firebase_admin.db |
| Storage | Store QR Code images | Binary (PNG) | firebase_admin.storage |
Why do you need two services?
- Database: Suitable for structured data, supports real-time querying and updating
- Storage: Suitable for large binary files, provides CDN acceleration
Data flow:
Gemini Vision → Database (structured data)
↓
QR Code generation
↓
Storage (image files)
↓
LINE Bot (image URL)
🔧 Challenges and Solutions Encountered
1. Firebase Storage Bucket Configuration Problem
Problem: When initializing the Firebase Admin SDK, there was an error because the Storage Bucket was not set.
Error message:
ValueError: Invalid None value for Firebase Storage bucket.
Cause analysis:
- Firebase Admin SDK defaults to only initializing Realtime Database
- Must explicitly specify
storageBucketwheninitialize_app() - Environment variables were not set correctly
Solution:
-
Add configuration in
config.py:
FIREBASE_STORAGE_BUCKET = os.environ.get("FIREBASE_STORAGE_BUCKET")
-
Add during initialization in
main.py:
firebase_config = {
"databaseURL": config.FIREBASE_URL,
}
if config.FIREBASE_STORAGE_BUCKET:
firebase_config["storageBucket"] = config.FIREBASE_STORAGE_BUCKET
firebase_admin.initialize_app(cred, firebase_config)
- Set environment variables during deployment:
--set-env-vars "...,FIREBASE_STORAGE_BUCKET=line-vertex.firebasestorage.app,..."
Lessons learned:
- Different services of Firebase require different configuration parameters
- Environment variables must be fully checked to avoid runtime errors
- Both the new format
.firebasestorage.appand the old format.appspot.comare supported
2. Permission Settings for Storage Rules
Problem: How to set Firebase Storage Rules so that Cloud Run can write, but QR Code images can be publicly read?
Initial idea:
// ❌ This will allow anyone to write
allow read, write: if true;
Correct solution:
Use the feature that Firebase Admin SDK will bypass Rules:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read: if true; // Anyone can read
allow write: if false; // Prevent client-side writing
}
}
}
Why is this feasible?
- ✅ Cloud Run uses Admin SDK, has full permissions (bypasses Rules)
- ✅
allow read: if trueallows the LINE Bot to access the image via URL - ✅
allow write: if falseprevents malicious clients from uploading files - ✅ The public permissions set by
blob.make_public()are still valid
Key learning:
- The difference in permissions between Admin SDK vs client-side SDK
- Storage Rules only affect client-side access
- Using Admin SDK for cloud services is the best practice
3. QR Code Size and Scanability Optimization
Problem: The generated QR Code is too small or too large, making it difficult to scan.
Experiment process:
| Parameter combination | Result | Problem |
|---|---|---|
box_size=5, border=1 |
Image too small | Difficult to scan with a phone |
box_size=15, border=4 |
Image too large | Distorted after LINE compression |
box_size=10, border=2 |
✅ Moderate | Smooth scanning |
Final solution:
qr = qrcode.QRCode(
version=None, # Automatically adjust size
error_correction=qrcode.constants.ERROR_CORRECT_L, # Low error correction
box_size=10, # 10px per box
border=2, # 2 box border
)
Why choose ERROR_CORRECT_L (low error correction)?
- vCard data is relatively stable and will not be damaged
- Low error correction = QR Code is simpler = scan faster
- If you use high error correction (H), the QR Code will become very complex
Test results:
- ✅ iPhone camera: scan in seconds
- ✅ Android camera: scan in seconds
- ✅ LINE built-in scanner: normal
4. vCard Special Character Handling
Problem: If there are special characters such as line breaks, commas, and semicolons in the notes, it will cause the vCard format to be incorrect.
Error example:
NOTE:This person is very important, remember to contact; next meeting time: 2025/11/15
The vCard parser will treat commas and semicolons as separators, leading to data confusion.
Solution:
if memo:
# Escape special characters in memo
escaped_memo = memo.replace('\n', '\\n').replace(',', '\\,').replace(';', '\\;')
vcard_lines.append(f'NOTE:{escaped_memo}')
vCard escape rules:
| Character | After escaping | Description |
|---|---|---|
Line break \n
|
\\n |
Line break in text |
Comma ,
|
\\, |
Avoid being used as a separator |
Semicolon ;
|
\\; |
Avoid being used as a separator |
Lessons learned:
- vCard has its own escape rules and cannot directly copy JSON
- The notes entered by the user may contain any character
- Fully test various special character situations
5. BytesIO Pointer Reset Problem
Problem: When uploading an image to Firebase Storage, sometimes an empty file is uploaded.
Error cause:
img_bytes = BytesIO()
img.save(img_bytes, format='PNG')
# ❌ At this time, the pointer is at the end of the file
blob.upload_from_file(img_bytes, content_type='image/png')
# ❌ Read from the end = read empty content
Solution:
img_bytes = BytesIO()
img.save(img_bytes, format='PNG')
img_bytes.seek(0) # ✅ Reset pointer to the beginning
blob.upload_from_file(img_bytes, content_type='image/png')
Why do you need seek(0)?
-
img.save()will move the pointer to the end of the file -
upload_from_file()reads from the current position - If you don't reset, it will read 0 bytes
Lessons learned:
- Pay attention to the pointer position when using BytesIO
- Remember to
seek(0)after writing before reading - This is a common beginner's trap
6. URL Requirements for LINE ImageMessage
Problem: Sometimes the QR Code cannot be displayed in LINE.
Cause analysis:
LINE Bot's ImageSendMessage has strict requirements for URLs:
- ✅ Must be HTTPS
- ✅ The image must be JPEG or PNG
- ✅ The URL must be publicly accessible
- ✅
original_content_urlandpreview_image_urlcan be the same
Correct usage:
image_message = ImageSendMessage(
original_content_url=image_url, # Firebase Storage's public URL
preview_image_url=image_url # You can use the same URL
)
Advantages of Firebase Storage:
- ✅ Automatically provides HTTPS URL
- ✅
blob.make_public()ensures public access - ✅ CDN acceleration, fast loading
- ✅
blob.public_urldirectly obtains the complete URL
🎯 Summary and Future Improvements
Project Highlights
-
🤖 AI-driven business card recognition
- Gemini Pro Vision API automatically parses business cards
- Supports Chinese business cards, high recognition accuracy
- Structured data storage, convenient for subsequent processing
-
📥 One-click add to address book
- vCard QR Code standard format
- iPhone/Android native support
- Scan and add, no manual input required
-
☁️ Firebase dual service integration
- Realtime Database stores structured data
- Storage stores QR Code images
- Admin SDK ensures security
-
🚀 Serverless architecture
- Deployed to Google Cloud Run
- Automatic scaling, pay-as-you-go
- Cold start optimization, fast response
-
🎨 User experience optimization
- LINE Flex Message beautiful interface
- Postback button interaction is smooth
- Clear usage instructions
Architecture Advantages
┌────────────────────────────────────────┐
│ Google Cloud Platform │
│ ┌──────────────────────────────────┐ │
│ │ Cloud Run (Serverless) │ │
│ │ - FastAPI │ │
│ │ - LINE Bot SDK │ │
│ │ - Firebase Admin SDK │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Gemini Pro Vision API │ │
│ │ - Business card image recognition │ │
│ │ - Structured data extraction │ │
│ └──────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────┐ │
│ │ Firebase Services │ │
│ │ - Realtime Database (Business card data) │ │
│ │ - Storage (QR Code images) │ │
│ └──────────────────────────────────┘ │
└────────────────────────────────────────┘
Practical Experience Sharing
1. Selection of Firebase Services
When to use Realtime Database?
- ✅ Structured data (JSON)
- ✅ Need real-time querying and updating
- ✅ Small data volume (business card information)
- ✅ Need simple query logic
When to use Firebase Storage?
- ✅ Binary files (images, videos, PDFs)
- ✅ Need public access URL
- ✅ Need CDN acceleration
- ✅ Larger file size
The best combination for this project:
Business card text data → Realtime Database
QR Code images → Storage
2. Practicality of vCard Standards
vCard is an underestimated standard:
- ✅ Cross-platform: All devices are supported
- ✅ No APP required: No need to install additional software
- ✅ Standardized: A mature standard with 30 years of history
- ✅ Extensible: Supports photos, social media, etc.
Usage scenarios far beyond business cards:
- Email signature files
- Website "Contact Us" page
- Conference registration system
- Social media profiles
3. Design Philosophy of QR Codes
Good QR Code design:
- ✅ Moderate size (10-15 px per module)
- ✅ Minimum border (2 modules)
- ✅ Low error correction (if the content is stable)
- ✅ High contrast (black and white is best)
Avoid over-design:
- ❌ Add Logo (increases scanning difficulty)
- ❌ Use color (easy to distort)
- ❌ Over-artistic (reduces readability)
4. Firebase Admin SDK vs Client-Side SDK
| Feature | Admin SDK | Client-side SDK |
|---|---|---|
| Execution environment | Server-side | Browser/Phone |
| Permissions | Full permissions (bypasses Rules) | Restricted by Rules |
| Authentication | Service Account | User authentication |
| Applicable scenarios | Cloud Run, Cloud Functions | Web App, Mobile App |
| Security | High (does not expose credentials) | Requires Rules protection |
Choice for this project:
- ✅ Use Admin SDK (Cloud Run environment)
- ✅ Storage Rules set to
write: false - ✅ Admin SDK can still write (bypasses Rules)
5. Best Practices for Gemini API
Prompt design skills:
# ✅ Good Prompt
"""
This is a business card, you are a business card secretary. Please organize the following information into json for me.
If you can't see it, please fill in N/A
Just json:
name, title, address, email, phone, company.
"""
# ❌ Bad Prompt
"Extract name, title, company from this business card"
Why is the first one better?
- ✅ Role setting: "You are a business card secretary" lets AI understand the task
- ✅ Clear format: Requires JSON, no other instructions
- ✅ Fault tolerance: Fill in N/A when it cannot be recognized
- ✅ Chinese instructions: More accurate when processing Chinese business cards
Future Improvement Directions
1. Feature Expansion
Short term (1-2 weeks):
- QR Code adds company Logo (improve brand recognition)
- Support multiple QR Code style selections
- QR Code download as a file (not just image links)
- Batch generate QR Codes for multiple business cards
Mid-term (1-2 months):
- Integrate NFC virtual business cards (iPhone Wallet)
- <

Top comments (0)