How are you uploading the image; are you sending a base64_encoded image; or is the API not only handling application/json?
API - Image gets uploaded with Zero Size
I am using Laravel Sanctum as the API and Flutter as the frontend.
The image is getting uploaded with Zero Size and i am trying to figure out whether it is an issue at the Laravel API side or the frontend(Flutter)
Anybody have any experience going through this kind of an issue ?
public function store(Request $request)
{
//validate fields
$attrs = $request->validate([
'body' => 'required|string'
]);
$image = $this->saveImage($request->image, 'posts');
$post = Post::create([
'body' => $attrs['body'],
'user_id' => auth()->user()->id,
'image' => $image
]);
return response([
'message' => 'Post created',
'post' => $post
], 200);
}
@tykus Sending base64_encoded image
This code if from the client side(Flutter)
// Get base64 encoded image
String? getStringImage(File? file) {
if (file == null) return null ;
return base64Encode(file.readAsBytesSync());
}
@zaster okay so you are sending a String, so I suppose the payload is JSON, what does that look like? And how are you handling that image in saveImage method?
The paylod is JSON and the flutter code looks like this
lib\models\post.dart
class Post {
.
.
.
factory Post.fromJson(Map<String, dynamic> json) {
return Post(
id: json['id'],
body: json['body'],
image: json['image'],
likesCount: json['likes_count'],
commentsCount: json['comments_count'],
selfLiked: json['likes'].length > 0,
user: User(
id: json['user']['id'],
name: json['user']['name'],
image: json['user']['image']
)
);
}
}
lib\screens\post_form.dart
class _PostFormState extends State<PostForm> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _txtControllerBody = TextEditingController();
bool _loading = false;
File? _imageFile;
final _picker = ImagePicker();
Future getImage() async {
final pickedFile = await _picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null){
setState(() {
_imageFile = File(pickedFile.path);
});
}
}
void _createPost() async {
String? image = _imageFile == null ? null : getStringImage(_imageFile);
ApiResponse response = await createPost(_txtControllerBody.text, image);
if(response.error == null) {
if(!mounted) return;
Navigator.of(context).pop();
}
else if (response.error == unauthorized){
logout().then((value) => {
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context)=>const Login()), (route) => false)
});
}
else {
if(!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('${response.error}')
));
setState(() {
_loading = !_loading;
});
}
}
lib\services\post_service.dart
// Create post
Future<ApiResponse> createPost(String body, String? image) async {
ApiResponse apiResponse = ApiResponse();
try {
String token = await getToken();
final response = await http.post(Uri.parse(postsURL),
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
}, body: image !=null ? {
'body': body,
'image': image
} : {
'body': body
});
// here if the image is null we just send the body, if not null we send the image too
switch(response.statusCode){
case 200:
apiResponse.data = jsonDecode(response.body);
break;
case 422:
final errors = jsonDecode(response.body)['errors'];
apiResponse.error = errors[errors.keys.elementAt(0)][0];
break;
case 401:
apiResponse.error = unauthorized;
break;
default:
// ignore: avoid_print
print(response.body);
apiResponse.error = somethingWentWrong;
break;
}
}
catch (e){
apiResponse.error = serverError;
}
return apiResponse;
}
Basically, i am trying to make this work
https://github.com/habibmhamadi/flutter-blog-app/tree/main/lib
https://github.com/habibmhamadi/laravel8-blog-api
https://www.youtube.com/playlist?list=PL0qWGthGFUCjFDgYI2k_-TqMNA7925c1s
@zaster I meant the Laravel code!
oops! Sorry.
The laravel code looks like this
app\Http\Controllers\PostController.php
//create a post
public function store(Request $request)
{
//validate fields
$attrs = $request->validate([
'body' => 'required|string'
]);
$image = $this->saveImage($request->image, 'posts');
$post = Post::create([
'body' => $attrs['body'],
'user_id' => auth()->user()->id,
'image' => $image
]);
return response([
'message' => 'Post created',
'post' => $post
], 200);
}
app\Http\Controllers\Controller.php
.
.
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
public function saveImage($image, $path = 'public')
{
if($image)
{
return null;
}
$filename = time().'.png';
//save image
\Storage::disk($path)->put($filename, base64_decode($image));
//return the path
// Url is the base url exp: localhost:8000
return URL::to('/').'/storage/'.$path.'/'.$filename;
}
}
app\Models\Post.php
.
.
protected $fillable = [
'user_id',
'body',
'image'
];
@zaster does the base64 encoded string in the form of a data URL; or is it pure Base 64 encoding? Can you post a sample base64 encoded string here (something that is not sensitive or NSFW!)?
I have tested this in both local and production environment.
Local Environment - Tested using Postman
This is when only body parameter is given (No image uploaded). It is the same when an image is uploaded
Url : http://blog-api.test/api/posts?body=test
JSON
{
"message": "Post created",
"post": {
"body": "test",
"user_id": 2,
"image": "http://blog-api.test/storage/posts/1675852471.png",
"updated_at": "2023-02-08T10:34:31.000000Z",
"created_at": "2023-02-08T10:34:31.000000Z",
"id": 7
}
}
@zaster that looks like the result was successful? Anyway, I asked about the base64 encoded data you are sending to the application; not the result!
@tykus I am trying to figure out the way to get it ....
@zaster try logging $request->input('image'); or if you have Laravel Dump Server use that instead; or Laravel Telescope to inspect the Request
Form submission with image
JSON
{
"message": "Post created",
"post": {
"body": "test",
"user_id": 2,
"image": null,
"updated_at": "2023-02-08T14:57:42.000000Z",
"created_at": "2023-02-08T14:57:42.000000Z",
"id": 14
}
}
Telescope - Log
Nothing
Form Submission with NO image
JSON
{
"message": "Post created",
"post": {
"body": "test",
"user_id": 2,
"image": "http://blog-api.test/storage/posts/1675868588.png",
"updated_at": "2023-02-08T15:03:08.000000Z",
"created_at": "2023-02-08T15:03:08.000000Z",
"id": 15
}
}
Telescope - Log
base64_decode(): Passing null to parameter #1 ($string) of type string is deprecated in C:\web\blog-api\app\Http\Controllers\Controller.php on line 26
This is Line 26
\Storage::disk($path)->put($filename, base64_decode($image));
@zaster I am not interested in the result; I wanted to know the payload from Flutter
I have used Postman as the client (Instead of flutter becuase there might be issues with the emulator etc...)
and then included the Telescope - Log
Let me give it another try
Please or to participate in this conversation.